「React进阶」只用两个自定义 Hooks 就能替代 React-Redux ?

2025-05-29 0 23

「React进阶」只用两个自定义 Hooks 就能替代 React-Redux ?

前言

之前有朋友问我,React Hooks 能否解决 React 项目状态管理的问题。这个问题让我思索了很久,最后得出的结论是:能,不过需要两个自定义 hooks 去实现。那么具体如何实现的呢?那就是今天要讲的内容了。

通过本文,你能够学习以下内容:

  • useContext ,useRef ,useMemo,useEffect 的基本用法。
  • 如何将不同组件的自定义 hooks 建立通信,共享状态。
  • 合理编写自定义 hooks , 分析 hooks 之间的依赖关系。
  • 自定义 hooks 编写过程中一些细节问题。

带着如上的知识点,开启阅读之旅吧~

一 设计思路

首先,看一下要实现的两个自定义 hooks 具体功能。

  • useCreateStore 用于产生一个状态 Store ,通过 context 上下文传递 ,为了让每一个自定义 hooks useConnect 都能获取 context 里面的状态属性。
  • useConnect 使用这个自定义 hooks 的组件,可以获取改变状态的 dispatch 方法,还可以订阅 state ,被订阅的 state 发生变化,组件更新。

如何让不同组件的自定义 hooks 共享状态并实现通信呢?

首先不同组件的自定义 hooks ,可以通过 useContext 获得共有状态,而且还需要实现状态管理和组件通信,那么就需要一个状态调度中心来统一做这些事,可以称之为 ReduxHooksStore ,它具体做的事情如下:

  • 全局管理 state, state 变化,通知对应组件更新。
  • 收集使用 useConnect 组件的信息。组件销毁还要清除这些信息。
  • 维护并传递负责更新的 dispatch 方法。
  • 一些重要 api 要暴露给 context 上下文,传递给每一个 useConnect。

1 useCreateStore 设计

首先 useCreateStore 是在靠近根部组件的位置的, 而且全局只需要一个,目的就是创建一个 Store ,并通过 Provider 传递下去。

使用:

  1. conststore=useCreateStore(reducer,initState)

参数:

  • reducer :全局 reducer,纯函数,传入 state 和 action ,返回新的 state 。
  • initState :初始化 state 。

返回值:为 store 暴露的主要功能函数。

2 Store设计

Store 为上述所说的调度中心,接收全局 reducer ,内部维护状态 state ,负责通知更新 ,收集用 useConnect 的组件。

  1. constStore=newReduxHooksStore(reducer,initState).exportStore()

参数:接收两个参数,透传 useCreateStore 的参数。

3 useConnect设计

使用 useConnect 的组件,将获得 dispatch 函数,用于更新 state ,还可以通过第一个参数订阅 state ,被订阅的 state 改变 ,会让组件更新。

  1. //订阅state中的number
  2. constmapStoreToState=(state)=>({number:state.number})
  3. const[state,dispatch]=useConnect(mapStoreToState)

参数:

  • mapStoreToState:将 Store 中 state ,映射到组件的 state 中,可以做视图渲染使用。
  • 如果没有第一个参数,那么只提供 dispatch 函数,不会订阅 state 变化带来的更新。

返回值:返回值是一个数组。

  • 数组第一项:为映射的 state 的值。
  • 数组第二项:为改变 state 的 dispatch 函数。

4 原理图

「React进阶」只用两个自定义 Hooks 就能替代 React-Redux ?

二 useCreateStore

  1. exportconstReduxContext=React.createContext(null)
  2. /*用于产生reduxHooks的store*/
  3. exportfunctionuseCreateStore(reducer,initState){
  4. conststore=React.useRef(null)
  5. /*如果存在——不需要重新实例化Store*/
  6. if(!store.current){
  7. store.current=newReduxHooksStore(reducer,initState).exportStore()
  8. }
  9. returnstore.current
  10. }

useCreateStore 主要做的是:

  • 接收 reducer 和 initState ,通过 ReduxHooksStore 产生一个 store ,不期望把 store 全部暴露给使用者,只需要暴露核心的方法,所以调用实例下的 exportStore抽离出核心方法。
  • 使用一个 useRef 保存核心方法,传递给 Provider 。

三 状态管理者 —— ReduxHooksStore

接下来看一下核心状态 ReduxHooksStore 。

  1. import{unstable_batchedUpdates}from'react-dom'
  2. classReduxHooksStore{
  3. constructor(reducer,initState){
  4. this.name='__ReduxHooksStore__'
  5. this.id=0
  6. this.reducer=reducer
  7. this.state=initState
  8. this.mapConnects={}
  9. }
  10. /*需要对外传递的接口*/
  11. exportStore=()=>{
  12. return{
  13. dispatch:this.dispatch.bind(this),
  14. subscribe:this.subscribe.bind(this),
  15. unSubscribe:this.unSubscribe.bind(this),
  16. getInitState:this.getInitState.bind(this)
  17. }
  18. }
  19. /*获取初始化state*/
  20. getInitState=(mapStoreToState)=>{
  21. returnmapStoreToState(this.state)
  22. }
  23. /*更新需要更新的组件*/
  24. publicRender=()=>{
  25. unstable_batchedUpdates(()=>{/*批量更新*/
  26. Object.keys(this.mapConnects).forEach(name=>{
  27. const{update}=this.mapConnects[name]
  28. update(this.state)
  29. })
  30. })
  31. }
  32. /*更新state*/
  33. dispatch=(action)=>{
  34. this.state=this.reducer(this.state,action)
  35. //批量更新
  36. this.publicRender()
  37. }
  38. /*注册每个connect*/
  39. subscribe=(connectCurrent)=>{
  40. constconnectName=this.name+(++this.id)
  41. this.mapConnects[connectName]=connectCurrent
  42. returnconnectName
  43. }
  44. /*解除绑定*/
  45. unSubscribe=(connectName)=>{
  46. deletethis.mapConnects[connectName]
  47. }
  48. }

状态

  • reducer:这个 reducer 为全局的 reducer ,由 useCreateStore 传入。
  • state:全局保存的状态 state ,每次执行 reducer 会得到新的 state 。
  • mapConnects:里面保存每一个 useConnect 组件的更新函数。用于派发 state 改变带来的更新。

方法

负责初始化:

  • getInitState:这个方法给自定义 hooks 的 useConnect 使用,用于获取初始化的 state 。
  • exportStore:这个方法用于把 ReduxHooksStore 提供的核心方法传递给每一个 useConnect 。

负责绑定|解绑:

  • subscribe:绑定每一个自定义 hooks useConnect 。
  • unSubscribe:解除绑定每一个 hooks

负责更新:

  • dispatch:这个方法提供给业务组件层,每一个使用 useConnect 的组件可以通过 dispatch 方法改变 state ,内部原理是通过调用 reducer 产生一个新的 state 。
  • publicRender:当 state 改变需要通知每一个使用 useConnect 的组件,这个方法就是通知更新,至于组件需不需要更新,那是 useConnect 内部需要处理的事情,这里还有一个细节,就是考虑到 dispatch 的触发场景可以是异步状态下,所以用 React-DOM 中 unstable_batchedUpdates 开启批量更新原则。

四 useConnect

useConnect 是整个功能的核心部分,它要做的事情是获取最新的 state ,然后通过订阅函数 mapStoreToState 得到订阅的 state ,判断订阅的 state 是否发生变化。如果发生变化渲染最新的 state 。

  1. exportfunctionuseConnect(mapStoreToState=()=>{}){
  2. /*获取Store内部的重要函数*/
  3. constcontextValue=React.useContext(ReduxContext)
  4. const{getInitState,subscribe,unSubscribe,dispatch}=contextValue
  5. /*用于传递给业务组件的state*/
  6. conststateValue=React.useRef(getInitState(mapStoreToState))
  7. /*渲染函数*/
  8. const[,forceUpdate]=React.useState()
  9. /*产生*/
  10. constconnectValue=React.useMemo(()=>{
  11. conststate={
  12. /*用于比较一次dispatch中,新的state和之前的state是否发生变化*/
  13. cacheState:stateValue.current,
  14. /*更新函数*/
  15. update:function(newState){
  16. /*获取订阅的state*/
  17. constselectState=mapStoreToState(newState)
  18. /*浅比较state是否发生变化,如果发生变化,*/
  19. constisEqual=shallowEqual(state.cacheState,selectState)
  20. state.cacheState=selectState
  21. stateValue.current=selectState
  22. if(!isEqual){
  23. /*更新*/
  24. forceUpdate({})
  25. }
  26. }
  27. }
  28. returnstate
  29. },[contextValue])//将contextValue作为依赖项。
  30. React.useEffect(()=>{
  31. /*组件挂载——注册connect*/
  32. constname=subscribe(connectValue)
  33. returnfunction(){
  34. /*组件卸载——解绑connect*/
  35. unSubscribe(name)
  36. }
  37. },[connectValue])/*将connectValue作为useEffect的依赖项*/
  38. return[stateValue.current,dispatch]
  39. }

初始化

  • 用 useContext 获取上下文中, ReduxHooksStore 提供的核心函数。
  • 用 useRef 来保存得到的最新的 state 。
  • 用 useState 产生一个更新函数 forceUpdate ,这个函数只是更新组件。

注册|解绑流程

  • 注册:通过 useEffect 来向 ReduxHooksStore 中注册当前 useConnect 产生的 connectValue ,connectValue 是什么马上会讲到。subscribe 用于注册,会返回当前 connectValue 的唯一标识 name 。
  • 解绑:在 useEffect 的销毁函数中,可以用调用 unSubscribe 传入 name 来解绑当前的 connectValue

connectValue是否更新组件

  • connectValue :真正地向 ReduxHooksStore 注册的状态,首先用 useMemo 来对 connectValue 做缓存,connectValue 为一个对象,里面的 cacheState 保留了上一次的 mapStoreToState 产生的 state ,还有一个负责更新的 update 函数。
  • 更新流程 :当触发 dispatch 在 ReduxHooksStore 中,会让每一个 connectValue 的 update 都执行, update 会触发映射函数 mapStoreToState 来得到当前组件想要的 state 内容。然后通过 shallowEqual 浅比较新老 state 是否发生变化,如果发生变化,那么更新组件。完成整个流程。
  • shallowEqual :这个浅比较就是 React 里面的浅比较,在第 11 章已经讲了其流程,这里就不讲了。

分清依赖关系

  • 首先自定义 hooks useConnect 的依赖关系是上下文 contextValue 改变,那么说明 store 发生变化,所以重新通过 useMemo 产生新的 connectValue 。所以 useMemo 依赖 contextValue。
  • connectValue 改变,那么需要解除原来的绑定关系,重新绑定。useEffect 依赖 connectValue。

局限性

整个 useConnect 有一些局限性,比如:

  • 没有考虑 mapStoreToState 可变性,无法动态传入 mapStoreToState 。
  • 浅比较,不能深层次比较引用数据类型。

五 使用与验证效果

接下来就是验证效果环节,我模拟了组件通信的场景。

根部组件注入 Store

  1. import{ReduxContext,useConnect,useCreateStore}from'./hooks/useRedux'
  2. functionIndex(){
  3. const[isShow,setShow]=React.useState(true)
  4. console.log('index渲染')
  5. return<div>
  6. <CompA/>
  7. <CompB/>
  8. <CompC/>
  9. {isShow&&<CompD/>}
  10. <buttononClick={()=>setShow(!isShow)}>点击</button>
  11. </div>
  12. }
  13. functionRoot(){
  14. conststore=useCreateStore(function(state,action){
  15. const{type,payload}=action
  16. if(type==='setA'){
  17. return{
  18. …state,
  19. mesA:payload
  20. }
  21. }elseif(type==='setB'){
  22. return{
  23. …state,
  24. mesB:payload
  25. }
  26. }elseif(type==='clear'){//清空
  27. return{mesA:'',mesB:''}
  28. }
  29. else{
  30. returnstate
  31. }
  32. },
  33. {mesA:'111',mesB:'111'})
  34. return<div>
  35. <ReduxContext.Providervalue={store}>
  36. <Index/>
  37. </ReduxContext.Provider>
  38. </div>
  39. }

Root根组件

  • 通过 useCreateStore 创建一个 store ,传入 reducer 和 初始化的值 { mesA:'111',mesB:'111' }
  • 用 Provider 传递 store。

Index组件

  • 有四个子组件 CompA , CompB ,CompC ,CompD 。其中 CompD 是 动态挂载的。

业务组件使用

  1. functionCompA(){
  2. const[value,setValue]=useState('')
  3. const[state,dispatch]=useConnect((state)=>({mesB:state.mesB}))
  4. return<divclassName="component_box">
  5. <p>组件A</p>
  6. <p>组件B对我说:{state.mesB}</p>
  7. <inputonChange={(e)=>setValue(e.target.value)}
  8. placeholder="对B组件说"
  9. />
  10. <buttononClick={()=>dispatch({type:'setA',payload:value})}>确定</button>
  11. </div>
  12. }
  13. functionCompB(){
  14. const[value,setValue]=useState('')
  15. const[state,dispatch]=useConnect((state)=>({mesA:state.mesA}))
  16. return<divclassName="component_box">
  17. <p>组件B</p>
  18. <p>组件A对我说:{state.mesA}</p>
  19. <inputonChange={(e)=>setValue(e.target.value)}
  20. placeholder="对A组件说"
  21. />
  22. <buttononClick={()=>dispatch({type:'setB',payload:value})}>确定</button>
  23. </div>
  24. }
  25. functionCompC(){
  26. const[state]=useConnect((state)=>({mes1:state.mesA,mes2:state.mesB}))
  27. return<divclassName="component_box">
  28. <p>组件A:{state.mes1}</p>
  29. <p>组件B:{state.mes2}</p>
  30. </div>
  31. }
  32. functionCompD(){
  33. const[,dispatch]=useConnect()
  34. console.log('D组件更新')
  35. return<divclassName="component_box">
  36. <buttononClick={()=>dispatch({type:'clear'})}>清空</button>
  37. </div>
  38. }
  • CompA 和 CompB 模拟组件双向通信。
  • CompC 组件接收 CompA 和 CompB 通信内容,并映射到 mes1 ,mes2 属性上。
  • CompD 没有 mapStoreToState ,没有订阅 state ,state 变化组件不会更新,只是用 dispatch 清空状态。

效果

「React进阶」只用两个自定义 Hooks 就能替代 React-Redux ?

六 总结

本文通过两个自定义 hooks 实现了 React-Redux 的基本功能,这个模式在真实项目中可以使用吗?我觉得如果是小型项目,是完全可以使用的,对于大型项目还是用 React Redux 或者其他成熟的状态管理工具。

原文地址:https://mp.weixin.qq.com/s/SFmTiV6yEgjQpltb07Q5Xw

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 「React进阶」只用两个自定义 Hooks 就能替代 React-Redux ? https://www.kuaiidc.com/93130.html

相关文章

发表评论
暂无评论