当前位置: 首页 > 科技观察

React全局状态管理的三大底层机制

时间:2023-03-13 01:08:04 科技观察

现代前端框架都是基于组件来开发页面的。将页面按照逻辑关系划分成不同的组件,分别开发不同的组件,然后逐层组装,将根组件传入ReactDOM.render或者vue的$mount方法,会遍历整个组件树并渲染as对应的dom。组件支持传递一些参数进行自定义,也可以在内部保存一些交互状态,在参数和状态发生变化后自动重新渲染相应的dom部分。虽然在逻辑上分为不同的组件,但它们都是同一个应用程序的不同部分,彼此之间不可避免地要进行通信和协作。如果超过一层的组件通过参数进行通信,则中间层的组件必须透传这些参数。参数本来是用来自定义组件的,一些无意义的参数不要加进去进行通信。所以,对于组件通信,一般不会通过组件参数层层传递,而是将它们放在一个全局的地方,双方都可以访问。全局状态管理的具体解决方案可能有很多,但它们的底层机制无外乎三种机制:props、context、state。接下来,我们来探讨一下这三种方法是如何存储和传递全局状态的。我们可以通过一个全局对象来传递props,一个组件在里面存数据,另一个组件取出来进行通信。在组件中写入store中的数据的代码侵入性比较大,所以不能在每个使用store的组件中都添加一段这样的代码。我们可以将这些逻辑提取到高层组件中,并用它们来连接(connect)组件和存储。数据通过参数的方式注入到组件中,使得数据源对组件透明。这是react-redux做的:,mapDispatchToProps)(TodoApp)此外,redux还提供了中间件机制,可以拦截组件发送给store的action,执行一系列的异步逻辑。比较流行的中间件有redux-thunk、redux-saga、redux-obervable,它们支持不同的方式编写和组织异步流程,封装和复用异步逻辑。其他类似的全局状态管理库,如mobox、reconcil等,也是通过props将全局状态注入到组件中。context跨层组件通信一定要使用第三方方案吗?不,react本身也为这种通信提供了上下文机制。React.createContext的api会返回Provider和Consumer,分别用来提供状态和获取状态,同样通过props透传给目标组件。(这里的Consumer也可以换成useContextapi,功能一样,class组件用Provider,function组件用useContext。)看来和redux的方案基本没什么区别。事实上,主要区别在于上下文没有执行异步逻辑的中间件。.所以context方案适用于没有异步逻辑的全局数据通信,而redux适用于组织复杂的异步逻辑。示例代码如下:constthemes={light:{foreground:"#000000",background:"#eeeeee"},dark:{foreground:"#ffffff",background:"#222222"}};constThemeContext=React.createContext(themes.light);functionApp(){return();}functionToolbar(props){return(

);}functionThemedButton(){consttheme=useContext(ThemeContext);return(Iamstyledbythemecontext!);}不知道大家有没有想过,当props和state改变的时候,重新渲染组件是正常的,但是当context改变的时候怎么触发渲染呢?其实react内部已经做了处理。如果上下文的值发生变化,则遍历所有子组件,找到使用上下文值的组件,并触发其更新。因此,props、state和context都可以触发重新渲染。Stateredux和context解决方案,一个是第三方的,一个是内置的,都是通过props传值或者通过hooks获取值,但是都是在组件外部,而state是在组件内部组件,如何通过state全局状态共享呢?其实类组件的状态是做不到的,但是功能组件的状态可以,因为它是通过useState的hooksapi创建的,可以将useState提取到自定义的hooks中,然后将不同的功能组件导入到这里。importReact,{useState}from'react';constuseGlobalState=(initialValue)=>{const[globalState,setGlobalState]=useState(initialValue);return[globalState,setGlobalState];}functionComponentA(){const[globalState,setGlobalState]=useGlobalState({name:'aaa'});setGlobalState({name:bbb});return
{globalState}
}functionComponentA(){const[globalState,setGlobalState]=useGlobalState({name:'aaa'});return
{globalState}
}上面的代码可以共享全局状态吗?不可能的,因为现在每个组件都在自己的fiber.memorizedState里面放了一个新的对象,修改也修改自己的。然后将这两个useStates的初始值指向同一个对象?这样就可以在多个组件之间操作同一个数据。上面的代码需要修改:letglobalVal={name:''}constuseGlobalState=()=>{const[globalState,setGlobalState]=useState(globalVal);functionupdateGlobalState(val){globalVal=val;setGlobalState(val);}return[globalState,updateGlobalState];}这样每个组件创建的状态都指向同一个对象,全局状态也可以共享。但是这里有一个前提,就是只能修改对象的属性,不能修改对象本身。总结一下,目前前端页面的开发方式是将页面按照逻辑拆分成组件,分别开发每个组件,然后一层层组装起来,传入ReactDOM.render或者Vue的$mount进行渲染.可以通过props自定义组件,通过state保存交互状态,这些变化会自动重新渲染。此外,如果上下文发生变化,它还会找到使用上下文数据的子组件来触发重新渲染。组件相互协作,所以不可避免地要进行通信。Props是用来自定义组件的,不应该用来透传无意义的props,所以必须通过全局对象来传递。React本身提供了context的解决方案,createContext会返回Provider和Consumer,分别用来存储和读取数据。在功能组件中,也可以使用useContext代替Provider。context虽然可以共享全局状态,但是它没有异步逻辑的执行机制。当有复杂的异步逻辑时,还是得用redux,redux提供了一个中间件机制来组织异步流程,封装和复用异步逻辑。比如在redux-saga中,可以将异步逻辑封装到saga中进行复用。context和redux都支持通过props向组件注入数据,对组件透明无侵入。其实useState封装的自定义hooks也可以通过将初始值指向同一个对象来达到全局数据共享的目的,但是有限制,只能修改对象的属性,不能修改对象本身。其实这种实用性还是用context比较好,顺便提一下,你可以这样做。简单总结就是:context和redux都可以做全局状态管理,一个是内置的,一个是第三方的。没有异步逻辑就用context,有异步逻辑就用redux。

猜你喜欢