《ReactAdvanced》仅用两个自定义Hooks就能替代React-Redux?转载本文请联系前端分享♂。在前言之前,有朋友问我ReactHooks能不能解决React项目状态管理的问题。这个问题让我想了很久,最后的结论是:可以,但是需要两个自定义的hook来实现。那么如何实现呢?这就是我今天要讲的内容。通过本文,您可以了解到:useContext、useRef、useMemo、useEffect的基本用法。如何在不同组件的自定义钩子之间建立通信并共享状态。合理编写自定义钩子,分析钩子之间的依赖关系。编写自定义钩子过程中的一些细节。有了以上的知识点,我们就开始阅读之旅吧~1.设计思路首先我们来看一下要实现的两个自定义钩子的具体功能。useCreateStore用于生成一个状态Store,通过context上下文传递,使得每个自定义的hooksuseConnect都可以获取到上下文中的state属性。useConnect使用这个自定义的hooks组件来获取改变状态的dispatch方法,也可以订阅状态。当订阅状态改变时,组件被更新。如何让不同组件的自定义hooks共享状态,实现通信?首先,不同组件的自定义hooks可以通过useContext获取公共状态,同时也需要实现状态管理和组件通信,所以需要一个状态调度中心来统一做这些东西可以叫做ReduxHooksStore,具体做的事情如下follows:全局管理状态,状态变化,并通知相应的组件更新。使用useConnect组件收集信息。组件销毁也会清除此信息。维护并传递负责更新的调度方法。一些重要的API应该暴露给上下文并传递给每个useConnect。1useCreateStore设计首先,useCreateStore位于靠近根组件的位置,全局只需要一个。目的是创建Store并通过Provider传递。使用:conststore=useCreateStore(reducer,initState)参数:reducer:全局reducer,纯函数,传入state和action,返回一个新的state。initState:初始化状态。返回值:store暴露的main函数函数。2Store设计Store是上面提到的调度中心,接收全局reducer,内部维护状态,负责通知更新,使用useConnect收集组件。constStore=newReduxHooksStore(reducer,initState).exportStore()参数:接收两个参数,透传useCreateStore的参数。3useConnect设计使用useConnect的组件会得到更新状态的dispatch函数,也可以通过第一个参数订阅状态。如果订阅状态发生变化,组件将被更新。//订阅state中的数constmapStoreToState=(state)=>({number:state.number})const[state,dispatch]=useConnect(mapStoreToState)参数:mapStoreToState:将Store中的state映射到state的状态该组件,您可以用于视图渲染。如果没有第一个参数,则只提供dispatch函数,不会订阅状态变化带来的更新。返回值:返回值是一个数组。数组的第一项:映射状态的值。数组的第二项:改变状态的调度函数。4示意图2useCreateStoreexportconstReduxContext=React.createContext(null)/*用于生成reduxHooksstore*/exportfunctionuseCreateStore(reducer,initState){conststore=React.useRef(null)/*如果存在-不需要重新实例化Store*/if(!store.current){store.current=newReduxHooksStore(reducer,initState).exportStore()}returnstore.current}useCreateStore主要做的是:接收reducer和initState,通过ReduxHooksStore生成一个store,不要指望把所有的store到暴露给用户,只需要暴露核心方法,所以调用实例下的exportStore提取核心方法。使用useRef保存核心方法并将其传递给提供者。三个状态管理器——ReduxHooksStore接下来我们看一下核心状态ReduxHooksStore。import{unstable_batchedUpdates}from'react-dom'classReduxHooksStore{constructor(reducer,initState){this.name='__ReduxHooksStore__'this.id=0this.reducer=reducerthis.state=initStatethis.mapConnects={}}/*需要是通过外部接口*/exportStore=()=>{return{dispatch:this.dispatch.bind(this),subscribe:this.subscribe.bind(this),unSubscribe:this.unSubscribe.bind(this),getInitState:this.getInitState.bind(this)}}/*获取初始化状态*/getInitState=(mapStoreToState)=>{returnmapStoreToState(this.state)}/*更新需要更新的组件*/publicRender=()=>{unstable_batchedUpdates(()=>{/*批量更新*/Object.keys(this.mapConnects).forEach(name=>{const{update}=this.mapConnects[name]update(this.state)})})}/*更新状态*/dispatch=(action)=>{this.state=this.reducer(this.state,action)//批量更新this.publicRender()}/*注册每个connect*/subscribe=(connectCurrent)=>{constconnectName=this.name+(++this.id)this.mapConnects[connectName]=connectCurrentreturnconnectName}/*Unbind*/unSubscribe=(connectName)=>{deletethis.mapConnects[connectName]}}statereducer:这个reducer是一个全局的reducer,通过useCreateStore传入state:全局保存的状态,每次执行reducer都会得到一个新的state。mapConnects:存放了各个useConnect组件的update函数。用于调度状态变化带来的更新。该方法负责初始化:getInitState:该方法被自定义钩子的useConnect用来获取初始化状态。exportStore:该方法用于将ReduxHooksStore提供的核心方法传递给各个useConnect。负责绑定|解除绑定:订阅:绑定各个自定义钩子useConnect。unSubscribe:取消绑定各个钩子。负责更新:dispatch:该方法提供给业务组件层。每个使用useConnect的组件都可以通过dispatch方法改变状态。内部原理是通过调用reducer生成新的state。publicRender:当状态改变时,需要通知每一个使用useConnect的组件。这个方法是通知更新。至于组件是否需要更新,那是useConnect内部需要处理的事情。这里还有一个细节,就是要考虑dispatch的触发场景。它处于异步状态,所以使用React-DOM中的unstable_batchedUpdates来启用批量更新原理。4.useConnectUseConnect是整个功能的核心部分。它需要做的是获取最新状态,然后通过订阅函数mapStoreToState获取订阅状态,判断订阅状态是否发生变化。如果发生变化,呈现最新状态。exportfunctionuseConnect(mapStoreToState=()=>{}){/*获取Store内部的重要函数*/constcontextValue=React.useContext(ReduxContext)const{getInitState,subscribe,unSubscribe,dispatch}=contextValue/*用于传递给业务组件state*/conststateValue=React.useRef(getInitState(mapStoreToState))/*渲染函数*/const[,forceUpdate]=React.useState()/*generate*/constconnectValue=React.useMemo(()=>{conststate={/*用于比较一次dispatch中新状态和之前状态是否发生变化*/cacheState:stateValue.current,/*更新函数*/update:function(newState){/*获取订阅状态*/constselectState=mapStoreToState(newState)/*浅比较状态是否改变,如果改变,*/constisEqual=shallowEqual(state.cacheState,selectState)state.cacheState=selectStatestateValue.current=selectStateif(!isEqual){/*Update*/forceUpdate({})}}}returnstate},[contextValue])//ContextValue作为依赖。React.useEffect(()=>{/*组件挂载-注册connect*/constname=subscribe(connectValue)returnfunction(){/*组件卸载-unbindconnect*/unSubscribe(name)}},[connectValue])/*使用connectValue作为useEffect*/return[stateValue.current,dispatch]}的依赖初始化useContext获取上下文中ReduxHooksStore提供的核心功能。使用useRef保存获得的最新状态。使用useState生成一个更新函数forceUpdate,它只更新组件。报名|解绑过程注册:将当前useConnect产生的connectValue通过useEffect注册到ReduxHooksStore中,connectValue是什么后面会讲到。subscribe用于注册,会返回当前connectValue的唯一标识名。unbind:在useEffect的destroy函数中,可以调用unSubscribe传入name解除绑定当前connectValueconnectValue是否更新组件object,里面的cacheState保留了上次mapStoreToState产生的state,还有update函数负责更新。Update过程:当ReduxHooksStore中触发dispatch时,会执行connectValue的每次update,update会触发映射函数mapStoreToState获取当前组件想要的state内容。然后使用shallowEqual浅比较新旧状态是否发生变化,如果发生变化则更新组件。完成整个过程。shallowEqual:这个浅比较就是React中的浅比较。它的过程在第11章已经讲过了,这里不再赘述。区分依赖关系。首先,自定义hooksuseConnect的依赖是context值发生变化,也就是说store发生了变化,所以又通过useMemo生成了一个新的connectValue。所以useMemo依赖于contextValue。如果connectValue发生变化,需要解除原来的绑定关系,重新绑定。useEffect取决于connectValue。局限性整个useConnect有一些局限性,比如:没有考虑mapStoreToState的可变性,不能动态传入mapStoreToState。浅比较,不能深度比较引用数据类型。5使用及验证效果接下来就是验证效果了。我模拟了组件通信的场景。根组件注入Storeimport{ReduxContext,useConnect,useCreateStore}from'./hooks/useRedux'functionIndex(){const[isShow,setShow]=React.useState(true)console.log('indexrendering')return
组件A
组件B对我说:{state.mesB}