当前位置: 首页 > Web前端 > HTML

手写一个React状态管理工具迷你版

时间:2023-03-28 11:11:57 HTML

手写一个React状态管理工具迷你版目前React中有很多各种各样的状态管理工具,比如:ReactReduxMobxHox每个状态管理工具都有层出不穷的相同API和使用方法都有一定的学习成本,这些状态管理工具也有一定的复杂性,并不是特别简单。在开发者眼里,只有更好用,才会有更多人使用。Vue流行不就是因为简单易上手吗?有时候我们只需要一个全局状态,放置一些状态和改变状态的函数就可以了,也达到了简化的原则。让我们一起实现最简单的状态管理工具。这个状态管理工具的核心是使用ContextAPI。在理解本文之前,你必须先了解并熟悉这个API的用法。首先,让我们看看这个状态管理工具是如何使用的。假设有一个计数器状态,那么我们通过加法和减法两种方法修改计数器。也就是说,我们需要使用一个计数器状态,以及两个方法来修改这个状态。在React函数组件中,我们使用useState方法来初始化一个状态,因此我们可以很容易地写出如下代码:import{useState}from'react'constuseCounter=(initialCount=0)=>{const[count,setCount]=使用状态(初始计数);constincrement=()=>setCount(count+1);const减量=()=>setCount(count-1);return{count,increment,decrement}}导出默认useCounter;现在,让我们创建一个组件来使用这个useCounter钩子函数,如下所示:);返回({count}addplus

)}然后在根组件App中使用,如下:importReactfrom'react'constApp=()=>{return(
)}这样一个计数器组件就搞定了,但是真的就这样吗?首先我们要知道计数器组件的状态应该是一致的,也就是说我们的计数器组件应该共享同一个状态,那么如何共享同一个状态呢?这时候就需要Context出来修改上面的组件了。我们在根组件App中初始化状态并将其传递给子组件。首先修改App根组件的代码如下:新建一个CounterContext.ts文件,代码如下:constCounterContext=createContext();导出默认CounterContext;importReact,{createContext}from'react'importCounterContextfrom'./CounterContext'constApp=()=>{const{count,increment,decrement}=useCounter();return(
)}然后我们也修改Counter组件代码如下:返回({count}addplus
)}这样我们就可以共享计数状态,无论在子组件中使用多深都没有问题,但这不是结束,继续这种使用虽然解决了共享状态的问题,但是我们发现在使用的时候需要额外传入一个contextname,所以我们需要把它包裹起来。最后我们只需要这样使用即可:constCounter=createModel(useCounter);exportdefaultCounter;const{Provider,useModel}=Counter;那么我们的App组件应该是这样的:importReact,{createContext}from'react'importcounterfrom'./Counter'return()}继续修改我们的Counter组件如下:importReact,{useContext}from'react'从'./Counter'导入计数器constCounter=()=>{const{count,increment,decrement}=counter.useModel();返回({count}addplus)}通过上面代码的展示,我们其实明白了我们只是将useContext和createContext构建到我们封装的Model中。接下来,我们就来揭开这个状态管理工具的神秘面纱,首先需要用到React相关的API,所以我们需要导入。如下:importtypeimporttype{ReactNode,ComponentType}from'react';从“反应”导入{createContext,useContext};接下来,定义一个唯一的标识符来确定传入的Context,而这个是用来确定使用Context时使用的是否正确。constEMPTY:唯一符号=Symbol();接下来我们需要定义Provider的类型。如下:exportinterfaceModelProviderProps{initialState?:Statechildren:ReactNode}上面我们定义了context的state类型,是一个泛型,参数是state的类型。默认初始化为undefined类型,children为defined类型,因为Provider的子节点是React节点,所以定义为ReactNode类型。然后是我们的Model类型,如下:exportinterfaceModel{Provider:ComponentType>useModel:()=>Value}这个也很好理解,因为Model暴露了两个东西,第一个是Provider,第二个是useContext,改个名字就行了,定义两个类型就够了。下一步是实现我们的核心函数createModel函数。让我们一步一步来。首先当然是定义这个函数。注意类型,如下:exportconstcreateModel=(useHook:(initialState?:State)=>Value):Model=>{//核心代码}看不懂的地方上面函数的一部分应该是类型的定义。我们的createModel函数传入一个hook函数,hook函数传入一个state作为参数,然后返回值就是我们定义的Model泛型,参数类型就是我们定义的函数的泛型。接下来,我们要做的自然是创建一个context,如下://Createacontextconstcontext=createContext(空);然后我们需要创建一个Provider函数,它本质上是一个React组件,如下:constProvider=(props:ModelProviderProps)=>{//这里使用ModelProvider主要是因为不能和定义的函数冲突名称const{Provider:ModelProvider}=context;const{initialState,children}=props;constvalue=useHook(initialState);return({children})}这里也很好理解,其实就是通过父组件获取初始状态和子节点,而Provider组件是从上下文,然后返回。注意,我们的值是传入的自定义钩子函数包裹的值。第三步,我们需要定义一个钩子函数来获取这个自定义的Context,如下:constuseModel=():Value=>{constvalue=useContext(context);//这里检查用户是否正确使用了Providerif(value===EMPTY){//抛出异常,用户没有用Provider包裹组件thrownewError('Componentmustbewrappedwith');}//returncontextreturnvalue;}this函数的实现也很好理解,就是获取上下文,判断上下文是否使用正确,然后返回。最后我们在这个函数内部返回这两个东西,也就是返回Provider和useModel这两个函数。如下:return{Provider,useModel}结合以上所有代码,createModel函数就完成了。最后,我们合并所有的代码,状态管理工具就完成了。//导入类型importtype{ReactNode,ComponentType}from'react';import{createContext,useContext}from'react';constEMPTY:uniquesymbol=Symbol();exportinterfaceModelProviderProps{initialState?:Statechildren:ReactNode}exportinterfaceModel{Provider:ComponentType>useModel:()=>Value}exportconstcreateModel=(useHook:(initialState?:State)=>Value):Model=>{//创建一个contextconstcontext=createContext(空);//定义Provider函数constProvider=(props:ModelProviderProps)=>{const{Provider:ModelProvider}=context;const{initialState,children}=props;constvalue=useHook(initialState);return({children})}//定义useModel函数constuseModel=():Value=>{constvalue=useContext(context);//在这里检查用户是否正确使用了Providerif(value===EMPTY){//抛出异常,用户无用ProviderwrappedcomponentthrownewError('Componentmustbewrappedwith');}//返回上下文返回值;}return{Provider,useModel};}更进一步,我们导出一个useModel函数,如下:exportconstuseModel=(model:Model):Value=>{returnmodel.useModel();}至此,我们整个状态管理工具就完成了,使用起来也很简单。在很多轻量级的共享状态项目中,我们不再需要使用像Redux这样更复杂的状态管理工具。当然,这个想法不是我自己的。文章末尾注明出处。本文分析了源码。源地址。PS:本文源码来自unstated-next。