【React】React Redux

【React】React Redux
0. 概述Redux 是一个“单一数据源 纯函数更新 单向数据流”的状态管理方案。基础特点单向数据流view 发出 actionstore 调用 reducer 计算出新的 state若 state 产生变化则调用监听函数重新渲染 view单一数据源只有一个 Storestate 是只读的每次状态更新之后只能返回一个新的 state没有 dispatcher而是在 store 中集成了 dispatch 方法store.dispatch() 是view 发出 action 的唯一途径支持使用中间件Middleware管理异步数据流redux 的发展redux 是一个独立的第三方库之后 react 官方在 redux 的基础上推出了 react-redux全面拥抱 hooks。此外redux 官方也推出了 redux toolkit简化使用 redux 的过程因为一般在 react 应用中我们使用 react-redux redux toolkit 。redux 概念直白解释1.Store仓库想象成一个大柜子里面放着整个应用需要的所有数据。任何组件想要数据都来这个柜子里拿。2.State状态就是柜子里的东西。比如用户信息、商品列表、当前页面等等。每个数据都是柜子里的一个物品。3.Action动作想象成一张便签纸上面写着我要干什么。比如“我要添加一个商品”“我要删除一个用户”“我要更新页面标题”4.Reducer整理员这是一个专职的整理员他收到便签纸Action后会根据指示去整理柜子Store里的东西。比如收到添加商品的便签他就把新商品放进柜子里。5.Dispatch投递就是把便签纸投递给整理员的过程。组件想要改变数据就写一张便签纸然后投递出去。6.Thunk延迟动作有时候整理员需要先去做别的事情比如去仓库拿东西做完后再回来整理柜子。Thunk 就是允许整理员先出门办事回来再整理。1. Redux 快速上手Redux 是 React 最常用的集中状态管理工具类似于Vue中的PiniaVuex可以独立于框架运行。npmi reduxbuttoniddecrement-/buttonspanidcount0/spanbuttonidincrement/buttonscriptsrchttps://unpkg.com/reduxlatest/dist/redux.min.js/scriptscript// 1. 定义 reducer 函数// 根据不同的 action 对象返回不同的 state// state 管理数据的初始状态// action 对象的 type 属性标记需要做的修改操作functionreducer(state{count:0},action){switch(action.type){caseINCREMENT:// state 是对象所以返回的数据也是对象return{count:state.count1}caseDECREMENT:return{count:state.count-1}default:returnstate}}// 2. 使用reducer函数生成store实例conststoreRedux.createStore(reducer)// 3. 通过 store 实例的 subscribe 订阅数据变化// 回调函数在每一次 state 发生变化时自动执行store.subscribe((){console.log(store.getState())document.getElementById(count).innerTextstore.getState().count})// 4. 通过 store 的 dispatch 函数提交 action 的更改状态constinBtndocument.getElementById(increment)inBtn.addEventListener(click,(){// 匹配的是 action 对象所以传入 action 对象store.dispatch({type:INCREMENT})})// 减constdBtndocument.getElementById(decrement)dBtn.addEventListener(click,(){store.dispatch({type:DECREMENT})})/script2. React 中使用 ReduxReact Redux 官方入门文档https://react-redux.js.org/tutorials/quick-start由于我们使用的是 TypeScript还要参考 TypeScript 的快速启动文档https://react-redux.js.org/tutorials/typescript-quick-start。2.1 配制环境Redux ToolkitRTK- 官方推荐编写Redux逻辑的方式简化书写方式react-redux - 用来链接 Redux 和 React组件的中间件npx create-react-app react-redux-demo npm i reduxjs/toolkit react-redux2.2 使用 RTK react-reduxSlice 是 Redux Toolkit 中的概念它将状态和相关的 reducer 逻辑组织在一起便于模块化管理。每个 slice 通常代表应用中的一部分状态如用户、产品、购物车等。在没有 Redux Toolkit 和 Slice 之前传统的 Redux 开发需要定义 action types、action creators 和 reducer 函数所有这些通常需要在不同的文件中编写增加了代码的复杂性和维护成本。创建 counterStore// /store/modules/counterStore.jsimport{createSlice}fromreduxjs/toolkitconstcounterStorecreateSlice({// 模块名称唯一name:counter,// 初始 stateinitialState:{count:1},// 修改数据的同步方法 支持直接修改reducers:{increment(state){// 不需要返回一个 state对象可以直接修改得益于内置了immer中间件state.count},decrement(state){state.count--},addToNum(state,action){state.countaction.payload}}})// 解构出 actionCreater 函数// action 具有两个值 一个是 payload 另一个是 type// 也很好理解一个行为具有行为的类型type和具体的实施payload 传递值const{increment,decrement,addToNum}counterStore.actions// 获取 reducer 函数constcounterReducercounterStore.reducer// 导出export{increment,decrement,addToNum}exportdefaultcounterReducer疑问一reducers 是什么东西state状态数据 和 action操作状态数据的行为 和它又有什么关系疑问二为什么只导出 reducer createSlice 和 configureStore 的关系是什么先说说英文里面他们仨都是什么意思reducer归纳这里是指归纳了 state 和 action 的一个东西state状态action行为/** * 详细讲讲这个抽象的东西 * reducers 对象中的函数 相当于一个函数中的一个个根据不同 action 执行的 case 语句 * reducer(state initialState, action) { * case ADD: * // 获取 action 根据 action 修改 state * return state action.payload; * case MINUS: * return state - action.payload; * } */所以在 slice 分片的数据中我们需要给 store 只是一个 reducer 一个归纳好的东西就不要导出 state 和 action了。createSlice 返回值是一个对象里面包含所有的 actions。 而 state 又是 和 action 紧密关联的。 所以这时我们只需要导出给 store 这个大仓库中即可获取和操作所有的状态数据。疑问三导出的 actions 为什么不能直接函数调用呢每一个函数都有自己的作用域同时具有自己的参数和返回值。那么现在有一个问题参数和返回值谁来保证答案是 patch 所以就有了一句话派发行为patch action疑问四异步请求的处理这里确实是 redux 比较麻烦的一个地方默认 redux 不能在 reducers 中处理异步而在外部处理 或者使用自带的一个方法 createAsyncThunk 。createAsyncThunk 可以被认为是一个 action只不过它是异步的。进而正常通过 dispatch 派发即可。但是提到异步就免不了有状态产生(pending/fulfilled/rejected)所以结果并不能被 reducers 正常归纳处理。这时就需要引入 extraReducers 专门处理异步 action。它通过 builder 来添加 case可以在每一个状态执行该状态下的 action。两种写法参考官方文档https://toolkit.redux.js.cn/api/createSlice/// /store/modules/channelStore.jsimport{createSlice}fromreduxjs/toolkitimportaxiosfromaxios// 以下的对象参数的三个属性必须写 不写会报错constchannelStorecreateSlice({name:channel,initialState:{channelList:[]},reducers:{setChannels(state,action){state.channelListaction.payload}}})// 异步请求部分const{setChannels}channelStore.actions// 相当于在 redux 外部处理异步执行异步请求constfetchChannlList(){returnasync(dispatch){constresawaitaxios.get(http://geek.itheima.net/v1_0/channels)dispatch(setChannels(res.data.data.channels))}}export{fetchChannlList}constreducerchannelStore.reducerexportdefaultreducer另一种处理异步请求的方式redux 内部处理请求import{createAsyncThunk,createSlice}fromreduxjs/toolkit;import{getType}from../../api/type;exportconstgetTypeListcreateAsyncThunk(type/getTypeList,async(){constresponseawaitgetType()returnresponse.data.data})consttypeStorecreateSlice({name:type,initialState:{// 存储所有的类型typeList:[]},reducers:{},// 处理异步的 reducerextraReducers:(builder){builder.addCase(getTypeList.fulfilled,(state,action){state.typeListaction.payload})}})exportdefaulttypeStore.reducer在项目的 src 目录下新建 stores 目录用于存放所有的状态。然后在 stores 目录下新建 index.ts 文件创建一个 Redux Store// /store/index.jsimport{configureStore}fromreduxjs/toolkitimportcounterReducerfrom./modules/counterStoreexportdefaultconfigureStore({reducer:{// 注册子模块counter:counterReducer}})// store.getStore 是一个返回 reducer 类型的函数 返回值是 reducer 类型exporttype IRootStateReturnTypetypeofstore.getState;exporttype IRootDispatchtypeofstore.dispatch;// 自定义绑定类型的 useSelector 和 useDispatchexportconstuseAppSelector:TypedUseSelectorHookIRootStateuseSelector;exportconstuseAppDispatch:()IRootDispatchuseDispatch;为 React 注入 store// /index.jsimportReactfromreactimportReactDOMfromreact-dom/clientimportAppfrom./App// 导入storeimportstorefrom./store// 导入store提供组件Providerimport{Provider}fromreact-reduxReactDOM.createRoot(document.getElementById(root)).render(// 提供store数据Provider store{store}App//Provider)如果是 Next 项目import store from /stores import { Provider } from react-redux export default function RootLayout({ children, }: Readonly{ children: React.ReactNode; }) { return ( html langen body AntdRegistry Provider store{store} BasicLayout{children}/BasicLayout /Provider /AntdRegistry /body /html ); }在 React 组件中使用修改 store 中的数据// App.jsimport{useEffect}fromreactimport{useDispatch,useSelector}fromreact-redux// 导入actionCreaterimport{inscrement,decrement,addToNum}from./store/modules/counterStoreimport{fetchChannlList}from./store/modules/channelStorefunctionApp(){// useSelector 函数将 store 中的数据映射到组件中 counter 是 store 名字const{count}useSelector(statestate.counter)const{channelList}useSelector(statestate.channel)constdispatchuseDispatch()// 使用useEffect触发异步请求执行useEffect((){dispatch(fetchChannlList())},[dispatch])return(div classNameAppbutton onClick{()dispatch(decrement())}-/button{count}button onClick{()dispatch(inscrement())}/button{/* 变为10 和 变为20 */}button onClick{()dispatch(addToNum(10))}add To10/buttonbutton onClick{()dispatch(addToNum(20))}add To20/buttonul{channelList.map(itemli key{item.id}{item.name}/li)}/ul/div)}exportdefaultApp结合 ts 使用import{configureStore}fromreduxjs/toolkit;importcounterfrom/store/modules/counter;import{TypedUseSelectorHook,useSelector}fromreact-redux;conststoreconfigureStore({reducer:{counter}});// store.getStore 为一个返回 reducer 类型的函数 返回值是 reducer 类型exporttypeIRootStateReturnTypetypeofstore.getState;// 自定义绑定类型的 useSelectorexportconstuseAppSelector:TypedUseSelectorHookIRootStateuseSelector;exportdefaultstore;2.3 总结createSlice用来接收 reducer 函数的对象、切片名称和初始状态值并自动生成切片 reducer, 并带有相应的actions。configureStore: 封装 createStore 以提供简化的配置选项和良好的默认值。createAsyncThunk: 接收一个动作类型字符串和一个返回 Promise 的函数并生成一个 pending/fulfilled/rejected 基于该 Promise 分派动作类型的 thunk。// 以下三个参数必须传递constchannelStorecreateSlice({name:channel,initialState:{channelList:[]},reducers:{setChannels(state,action){state.channelListaction.payload},}})// 解构出来 actionCreator 函数修改状态数据的函数const{setChannels}channelStore.actions// 获取 reducerconstreducerchannelStore.reducer// 以按需导出的方式导出 actionCreator 生成 action 对象 的函数export{setChannels}// 导出 reducer 函数exportdefaultreducerconststoreconfigureStore({// 接收 子 reducer 并导入合并reducer:{counter:counterReducer,channel:channelReducer}})exportdefaultstorefunctionApp(){// 获取 reducer 并解构出 stateconst{count}useSelector(statestate.counter)// 生成提交 action 对象的 dispatch 函数用于辅助修改 stateconstdispatchuseDispatch()const{channelList}useSelector(statestate.channel)// useEffect 触发异步请求执行useEffect((){dispatch(fetchChannelList())},[dispatch]);return(div{/*dispatch 触发 actionCreater 修改数据*/}button onClick{()dispatch(decrement())}-/button{count}button onClick{()dispatch(increment())}/buttonbutton onClick{()dispatch(addToNum(10))}10/buttonul{channelList.map(item(li key{item.id}{item.name}/li))}/ul/div);}initialState 初始化 state数据状态reducers 修改状态数据的函数接收两个参数 state 和 action。而 action.payload 是传入的参数。组合 redux 和 reactProvider store{store}App//Provider异步操作配置同步修改状态的方法单独封装一个函数,在函数内部return一个新函数,在新函数中2.1 封装异步请求获取数据2.2调用同步actionCreator传入异步数据生成一个action对象,并使用dispatch提交异步获取数据同步修改数据constfetchChannelList(){// 这里可以直接使用 dispatchreturnasync(dispatch){constresawaitaxios.get(http://geek.itheima.net/v1_0/channels)dispatch(setChannels(res.data.data.channels))}}export{fetchChannelList}2.4 RTK 解决了 redux 的痛点代码冗余(Boilerplate)Store配置复杂rtk 内置了很多常用中间件手动组合 reducers手动应用 middleware 比如 Thunk, Saga手动配置 Redux DevToolsImmutable更新易错必须手动编写不可变更新逻辑rtk 内置了 immer.js异步操作处理官方未内置现在看来 React Hooks 出现后大量“本该是局部状态”的东西不再需要 Redux 了。再具体一点Redux 心智模型重样板代码多全局状态滥用维护成本高3.connectconnect是 React-Redux 提供的高阶组件HOC用于把 Redux store 的 state 和 dispatch 注入到 React 组件中。简单来说就是connect 把 Redux 和 React 组件“连接”起来import{connect}fromreact-redux;functionCounter({count,add}){return(button onClick{add}{count}/button);}constmapStateToPropsstate({count:state.counter});constmapDispatchToPropsdispatch({add:()dispatch({type:ADD})});exportdefaultconnect(mapStateToProps,mapDispatchToProps)(Counter);connect 内部做了三件事订阅 store把 state 映射成 props触发组件更新性能优化connect 默认是“浅比较 精准更新”原因每个connect组件只订阅自己关心的 statemapStateToProps返回新 props 才会 re-render内部使用shallowEqual比useSelector更早、更成熟的性能方案connect vs HooksuseSelectorHooks 写法constcountuseSelector(statestate.counter);constdispatchuseDispatch();connect 是 HOC适合类组件和性能敏感场景Hooks 写法更简洁但更依赖开发者写好 selector 和 memo。store 改变 ↓ connect 订阅回调触发 ↓ 执行 mapStateToProps ↓ 浅比较 props ↓ 有变化 → 组件 re-renderRedux Toolkit 出来后 connect 已经不在重要了。