本文基于npx create-react-app创建
太久没看react,闲来无事重新捡起做一点笔记,希望对部分vue的同行想学习redux起到一些帮助
1.准备工作安装
1.安装项目插件
yarn add @craco/craco craco-less
2.修改package.json
中的scripts,将react-scripts
替换为craco
:
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
// ...
}
3.craco.config.js
根目录下创建或修改craco.config.js
来配置Less 以及@别名配置:
const CracoLessPlugin = require('craco-less');
const path = require('path');
module.exports = {
plugins: [
// 配置CracoLessPlugin插件,用于处理Less样式文件
{
plugin: CracoLessPlugin, // 引入的插件
options: {
lessLoaderOptions: {
// 配置Less加载器
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A' }, // 自定义Less变量,例如改变主题颜色
javascriptEnabled: true, // 启用Less中的JavaScript
},
},
},
},
],
webpack: {
configure: (webpackConfig) => {
webpackConfig.resolve.alias = {
...(webpackConfig.resolve.alias || {}),
'@': path.resolve(__dirname, 'src'),
};
return webpackConfig;
},
},
};
2.redux基础操作-同步
2.1.准备工作
2.1.1.src下创建view
2.1.2.view下创建两个文件夹(Home,About)
2.1.3.Home和About下创建Home.jsx/About.jsx
2.1.4.src下创建store文件夹
2.1.5.store下创建reducer1.js 用于单个模块化的reducer
2.1.6.store下创建reducer.js 用于合并reducer
2.1.7.store下创建store.js store的暴露文件
-src
-view
-Home
Home.jsx
-About
About.jsx
-store
-reducer1.js
-reducer.js
-store.js
2.2 编写reducer1.js文件
//初始化该模块的state
const initalState = 0
//reducer接受两个参数 state(初始值initalState) action:一个对象{type:"自定义的类型INCREMENT",payload:"携带的信息"}
const counterReducer=(state=initalState,action)=>{
//使用swicth判断匹配类型 return 对应的操作结果
Switch(action.type){
case "INCREMENT":
//注意这里不是直接改变state 而是返回对state操作的结果
return state+1;
default :
return state
}
}
//将counterReducer进行暴露 在reducer.js中引入进行合并操作
export default counterReducer
2.3 reducer.js 文件编写
//从redux引入合并函数
import {combineReducers} from "redux"
//引入reducer1.js中的模块化的reducer函数
import counterReducer from "./reducer1"
const reducer = combineReducers({
//state name:reducer name
//这里代表这个加载进去的模块名就是counter
//后续使用store.getState().counter可获取该模块的state
//connect connect(mapStateToProps,mapDispatchToProps)(Home)
//使用connect时 mapStateToProps(state) { return { counter: state.counter } }
counter:counterReducer
})
//暴露合并的reducer
export default reducer
2.4 store.js操作创建store
//从redux引入创建函数
import {createStore} from "redux"
//从reducer.js引入合并后的reducer
import rootReducer from "./reducer"
const store = createStore(rootReducer)
//暴露store
export default store
2.5.页面中的基础使用-同步(1)
2.5.1 Home.jsx store.dispatch使用1
//引入写好的store.js
import store from "@/store/store"
//从react-redux引入useSelect函数 用户获取store 中对应的state
import {useSelector} from "react-redux"
function Home(){
const counter = useSelector(state=>state.counter)
const increment = ()=>{
store.dispatch({type:"INCREMENT"})
}
return (
<>
<div>Home</div>
<div>reducer1中的counter为:{counter}</div>
{/*在页面上点击按钮后,counter会变化(每次+1)*/}
<button onClick={increment}>调用store.dispatch修改counter</button>
</>
)
}
export default Home
2.5.2 Home.jsx Home.jsx store.dispatch使用2
2.5.2.1 修改reducer1.js action.payload属性(携带的信息)
//初始化该模块的state
const initalState = 0
//reducer接受两个参数 state(初始值initalState) action:一个对象{type:"自定义的类型INCREMENT",payload:"携带的信息"}
const counterReducer=(state=initalState,action)=>{
//使用swicth判断匹配类型 return 对应的操作结果
Switch(action.type){
case "INCREMENT":
//注意这里不是直接改变state 而是返回对state操作的结果
//这里使用action的pyload属性(也就是携带的信息)
return state+action.payload;
default :
return state
}
}
//将counterReducer进行暴露 在reducer.js中引入进行合并操作
export default countReducer
2.5.2.2 action携带信息payload
//引入写好的store.js
import store from "@/store/store"
//从react-redux引入useSelect函数 用户获取store 中对应的state
import {useSelect} from "react-redux"
function Home(){
const counter = useSelect(state=>state.counter)
const increment = ()=>{
//增加payload
store.dispatch({type:"INCREMENT",payload:5})
}
return (
<>
<div>Home</div>
<div>reducer1中的counter为:{counter}</div>
{/*在页面上点击按钮后,counter会变化(每次+5)*/}
<button onClick={increment}>调用store.dispatch修改counter</button>
</>
)
}
export default Home
2.6 页面中的基础使用-同步(2)-高阶组件
2.6.1 需要修改index.js
//引入Provider
//从react-redux中引入 用户连接redux和react的插件
import {Provider} from "react-redux"
//引入store/store.js
import store from "@/store/store"
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
//使用Provider包裹App组件
<Provider store={store}>
<App />
</Provider>
);
我的App组件是这样的:
import { BrowserRouter, Routes, Route,Navigate } from "react-router-dom"
import Home from "@/view/Home/Home";
import About from "@/view/About/About"
import NotFound from "@/view/NotFound/NotFound"
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Navigate replace to="/home"></Navigate>}></Route>
<Route path="/home" element={<Home></Home>}></Route>
<Route path="/about" element={<About></About>}></Route>
<Route path="*" element={<NotFound />} /> {/* 使用'*'来匹配所有未找到的路由 */}
</Routes>
</BrowserRouter>
);
}
export default App;
2.6.2 修改Home.jsx文件
//从react-redux引入connect
import {connect} from "react-redux"
const mapStateToProps=(state)=>{
return {
counter:state.counter
}
}
const mapDispatchToProps=(dispatch)=>{
return{
increment:()=>{
dispatch({type:"INCREMENT",payload:5})
}
}
}
function Home(props){
//使用connect包裹的操作相当于将state和increment映射进了Home组件的props
const {counter,increment} = props
return (
<>
<div>Home</div>
<div>reducer1中的counter为:{counter}</div>
{/*在页面上点击按钮后,counter会变化(每次+5)*/}
<button onClick={increment}>调用store.dispatch修改counter</button>
</>
)
}
//使用connect参入参数回调函数mapStateToProps,mapDispatchToProps包裹Home组件后暴露 高阶函数用法
export default connect(mapStateToProps,mapDispatchToProps)(Home)
该使用效果与同步(1)一样
3.redux异步操作
1.安装中间件扩展dispatch功能
dispatch接受的是一个对象
也就是说redux分发任务的时候disptach接受的action是一个对象,当使用异步操作时,可以利用thunk中间件,让dispatch接受一个函数
安装redux-thunk插件
yarn add redux-thunk
2024-07-11补充:store需要导入applyMiddleware 和thunk
import {createStore,applyMiddleware } from "redux"
import thunk from "redux-thunk"
import rootReducer from "./reducer"
//创建Store 新增applyMiddleware(thunk)
const store = createStore(rootReducer,applyMiddleware(thunk))
export default store
2.原来的dispatch调用是这样的
const mapDispatchToProps=(dispatch)=>{
return{
increment:()=>{
//dispatch接受的是一个对象
dispatch({type:"INCREMENT",payload:5})
}
}
}
另一中接受对象的写法(等同于上面):
function incrementCounter(amount){
return {type:"INCREMENT",payload:amount}
}
const mapDispatchToProps=(dispatch)=>{
return{
//传入回调函数 回调函数会返回对象{type:"INCREMENT",payload:5}
increment:dispatch(incrementCounter(5))
}
}
另外,可将incrementCounter函数放入一个单独创建的actions文件夹下创建的action.js文件,达成模块化的操作
3.模块化actions,编写模拟异步请求
3.1 store文件夹下创建actions文件夹
3.2 actions文件夹下创建独立的action模块 action1.js
这里同理,可以将reducer.js和reducer1.js放入单独的reducer文件夹
-store
-actions
-action1.js
-reducer.js
-reducer1.js
-store.js
//新增一个常亮文件 用于放置action.type常量 可将之前的reducer Switch中的case 后的常亮提取出来放到此处,再从此处暴露引入到reducer文件中
-constant.js
3.3 constant.js文件 新增异步action.type
export const TEST_INCREMENT = "TEST_INCREMENT"
3.4 action1.js编写如下
//引入需要用的action.type
import { TEST_INCREMENT } from "@/store/constant"
//doSome 是一个模拟请求的函数 这里doSome函数可以提取到对应的api请求文件中
function doSome() {
return new Promise(r => {
setTimeout(() => {
r({ code: 0, data: 10 })
}, 2000)
})
}
//fetchTest是一个函数,它返回一个异步函数 函数中执行模拟请求 dispatch分发action 去执行对应的reducer操作
const fetchTest=()=>{//注意这里没有async 这里就是一个普通函数
//回调函数接受传递dispatch
return async(dispatch)=>{//这里返回的才是异步函数
let res = await doSome()
if(res.code===0){
//调用dispatch 传入action对象 data等于10 也就是说2s请求完成后,state会增加10 参考3.4 reducer1.js 新增action匹配
dispatch({type:TEST_INCREMENT,payload:res.data})
}
}
}
3.5 reducer1.js 新增action匹配
//从constant.js引入需要使用的action.type常量
import { TEST_INCREMENT } from "@/store/constant"
const initalState = 0
const counterReducer = (state = initalState, action) => {
switch (action.type) {
case "INCREMENT":
return state + action.payload;
//这里就是例子,直接使用引入的常量进行匹配
case TEST_INCREMENT:
return state + action.payload;
default:
return state;
}
}
export default counterReducer
3.6 Home.jsx修改如下 新增了incrementAsync函数 点击按钮执行
import React from 'react'
import { useNavigate } from "react-router-dom"
import { fetchTest} from "../../store1/actions1"
import { connect } from "react-redux"
function Home(props) {
const history = useNavigate()
const goToAbout = () => {
history("/about")
}
//从props中解构出来 connect映射的数据(状态以及函数)
const { counter, increment, incrementAsync } = props
return (
<div>
<header>Home</header>
<section>
<div></div>
<button onClick={increment}>修改state.counter{counter}</button>
<button onClick={incrementAsync}>异步改变counter{counter}</button>
<button onClick={goToAbout}>去about页面</button>
</section>
</div>
)
}
const mapStateToProps = (state) => {
return {
counter: state.counter
}
}
const mapDispatchToProps = (dispatch) => {
return {
increment: () => {
dispatch({ type: "INCREMENT", payload: 5 })
},
//这里 incrementAsync是一个函数 函数内部调用dispatch dispatch的参数就是fetchTest(),fetchTest()返回的是一个异步函数,这个异步函数接受dispatch,等待请求结果,然后使用dispatch分发对应的action 参考3.3 action1.js编写如下
incrementAsync:()=>dispatch(fetchTest())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home)
以上都是基于connect 高阶组件映射props 调用dispatch达到操作store的效果
此外还可以使用hook useSelect useDispatch达到操作store的效果
4.hook useSelector useDispatch
其他文件不变,Home.jsx使用hook操作
使用方法如下:
import React from 'react'
import { useNavigate } from "react-router-dom"
import { fetchUser} from "../../store1/actions1"
// import { connect } from "react-redux"
import {useSelector,useDispatch} from "react-redux"
export default function Home() {
const history = useNavigate()
const goToAbout = () => {
history("/about")
}
//使用useSelector获取对应的仓库状态
const counter = useSelector(state => state.counter)
//useDispatch获取dispatch
const dispatch = useDispatch()
const increment=()=>{
//dispatch分发action
dispatch({ type: "INCREMENT",payload:5 })
}
//dispatch 执行异步操作
const incrementAsync=()=>dispatch(fetchUser())
return (
<div>
<header>Home</header>
<section>
<div></div>
<button onClick={increment}>修改state.counter{counter}</button>
<button onClick={incrementAsync}>异步改变counter{counter}</button>
<button onClick={goToAbout}>去about页面</button>
</section>
</div>
)
}