当前位置: 首页 > 后端技术 > Node.js

再说说React的两个状态管理库,Redux&Recoil

时间:2023-04-03 21:14:49 Node.js

后台React是一个非常优秀的UI库。一开始,React只专注于UI层,对于全局状态管理并没有很好的解决方案,催生了Flux、Redux等优秀的状态管理工具Produce,久而久之又催生了一批新的状态管理工具。管理工具。简单梳理一下目前主流的一些状态管理工具:ReduxReactContext&useReducerMobxRecoilreact-sweet-statehox这些都是我接触过的,Npm上的现状和趋势对比:毫无疑问,React和Redux的结合是目前主流。五月的今天,一个叫Recoil.js的新成员进入了我的视野,带来了一些有趣的模型和概念。今天我们就把它和Redux做一个简单的对比,希望对大家有所启发。先看文中的Redux:ReduxReact-Redux架构图:这个模型比较简单,大家也比较熟悉。先用一个简单的例子,回头看下整个模型:actions.jsexportconstUPDATE_LIST_NAME='UPDATE_NAME';reducers.jsexportconstreducer=(state=initialState,action)=>{const{listName,tasks}=state;switch(action.type){case'UPDATE_NAME':{//...}default:{returnstate;}}};store.jsimportreducersfrom'../reducers';import{createStore}from'redux';conststore=createStore(reducers);exportconstTasksProvider=({children})=>({children});App.jsimport{TasksProvider}from'./store';importTasksfrom'./tasks';constReduxApp=()=>();Component//componentsimportReactfrom'react';import{updateListName}from'./actions';importTasksViewfrom'./TasksView';constTasks=(props)=>{const{tasks}=props;返回();};constmapStateToProps=(state)=>({tasks:state.tasks});constmapDispatchToProps=(dispatch)=>({updateTasks:(tasks)=>dispatch(updateTasks(任务))});导出默认连接(mapStateToProps,mapDispatchToProps)(任务);当然你也可以使用connect,react-redux提供了两个hook,useDispatch和useSelector,也很方便从'react-redux'导入{useDispatch,useSelector};constTasks=()=>{constdispatch=useDispatch();constname=useSelector(state=>state.name);constsetName=(name)=>dispatch({type:'updateName',payload:{name}});返回();};整个模型并不复杂,redux也推出了redux工具包,提供了createSlice方法来简化一些操作,例如://ActionexportconstUPDATE_LIST_NAME='UPDATE_LIST_NAME';//ActioncreatorexportconstupdateListName=(name)=>({type:UPDATE_LIST_NAME,payload:{name}});//Reducerconstreducer=(state='Myto-dolist',action)=>{switch(action.type){caseUPDATE_LIST_NAME:{const{name}=动作.有效载荷;返回名称;}默认值:{返回状态;}}};导出默认减速器;使用createSlice://src/redux-toolkit/state/reducers/list-nameimport{createSlice}from'@reduxjs/toolkit';常量列表NameSlice=createSlice({name:'listName',initialState:'todo-list',reducers:{updateListName:(state,action)=>{const{name}=action.payload;returnname;}}});exportconst{actions:{updateListName},}=listNameSlice;导出默认listNameSlice.reducer;通过createSlice可以减少一些不必要的代码,提升开发体验。但是,Redux还是有一些天生的缺陷:概念多,脑力负担大。属性需要一个一个pick,计算属性需要依赖reselect。还存在魔术字符串等一系列问题,使用起来繁琐易错,开发效率低下。触发更新的效率也比较差。对于store连接的组件,必须一个一个遍历,然后组件进行比较,拦截不必要的更新。这对于性能导向或者大型应用来说无疑是一场灾难。对于这种情况,React本身也提供了一种解决方案,就是众所周知的Context.{value=>/*rendersomethingbasedonthecontextvalue*/>在父节点添加一个Provider,在子节点添加一个Consumer,但是每增加一个item就需要增加一层Provider,而且越来越多:而且,使用起来有很多问题语境。对于使用useContext的组件来说,最突出的问题就是re-render。不过也有相应的优化方案:React-tracked。一个小例子://store.jsimportReact,{useReducer}from'react';import{createContainer}from'react-tracked';从'./reducers'导入{reducers};constuseValue=({reducers,initialState})=>useReducer(reducer,initialState);const{Provider,useTracked,useTrackedState,useUpdate}=createContainer(useValue);导出constTasksProvider=({children,initialState})=>({children});export{useTracked,useTrackedState,useUpdate};对应的,还有一个hooks版本:const[state,dispatch]=useTracked();constdispatch=useUpdate();conststate=useTrackedState();//...RecoilRecoil.js提供了另一种思路,它的模型是这样的:在React树上再创建一个正交树,提取每一项的状态。每个组件都有对应的状态,当数据更新时,相应的组件也会更新。Recoil将每条数据称为一个Atom,Atom是一个可订阅的可变状态单元。这可能有点抽象,让我们看一个简单的例子://index.jsimportReactfrom"react";importReactDOMfrom"react-dom";import{RecoilRoot}from"recoil";import"./index.css";从"./App"导入应用程序;从"./serviceWorker"导入*asserviceWorker;ReactDOM.render(,document.getElementById("root"));RecoilRoot提供原子在其中具有值的上下文。必须是使用任何Recoil挂钩的任何组件的祖先。多个根可能并存;原子将在每个根中具有不同的值。如果它们是嵌套的,最里面的根将完全掩盖任何外部根。RecoilRoot可以看作是顶级的Provider。Atoms假设现在要实现一个计数器:首先用useState实现它:importReact,{useState}from"react";constApp=()=>{const[count,setCount]=useState(0);return(setCount(count+1)}>增加setCount(count-1)}>减少

Countis{count}
);};exportdefaultApp;然后使用atom重写:importReactfrom"react";从“反冲”导入{原子,useRecoilState};constcountState=atom({键:“计数器”,默认值:0,});constApp=()=>{const[count,setCount]=useRecoilState(countState);return(setCount(count+1)}>增加setCount(count-1)}>减少
Countis{count}
);};exportdefaultApp;看到这里,你可能有一个初步的了解了原子的概念,到底是什么?Atom有一个简单的理解。Atom包含一组可以共享和修改的数据。组件可以订阅一个或多个原子,并在原子发生变化时触发重新渲染。constsomeState=atom({key:'uniqueString',default:[],});每个原子都有两个参数:key:用于在内部标识原子的字符串。该字符串相对于整个应用程序中的其他原子和选择器应该是唯一的。default:原子的初始值。原子是状态存储的最小单位。合理的设计是在保持最大灵活性的同时,使原子尽可能小。Recoil作者在ReactEurope视频中也介绍了后一种封装原子的方法:exportconstitemWithId=memoize(id=>atom({key:`item${id}`,default:{...},}));选择器的官方描述:“选择器是一个纯函数,它接受原子或其他选择器作为输入。当这些上游原子或选择器更新时,选择器函数将被重新评估。”选择器将atom作为参数,当atom发生变化时触发重新计算的纯函数。选择器具有以下参数:key:用于在内部标识原子的字符串。该字符串相对于整个应用程序中的其他原子和选择器应该是唯一的。get:function{get}作为对象传递,其中get是从另一个原子或选择器中检索值的函数。传递给此函数的所有原子或选择器都将隐式添加到选择器的依赖项列表中。set?:返回新的可写状态的可选函数。它作为对象{get,set}和新值传递。get是一个从其他原子或选择器中检索值的函数。set是一个设置原子值的函数,其中第一个参数是原子名称,第二个参数是新值。看具体的例子:importReactfrom"react";import{atom,selector,useRecoilState,useRecoilValue}from"recoil";constcountState=atom({key:"myCount",default:0,});constdoubleCountState=selector({key:"myDoubleCount",get:({get})=>get(countState)*2,});constinputState=selector({key:"inputCount",get:({get})=>get(doubleCountState),设置:({set},newValue)=>set(countState,newValue),});constApp=()=>{const[count,setCount]=useRecoilState(countState);constdoubleCount=useRecoilValue(doubleCountState);const[input,setInput]=useRecoilState(inputState);return(setCount(count+1)}>增加setCount(count-1)}>减少setInput(Number(e.target.value))}/>
计数为{count}
Doublecountis{doubleCount}
);};exportdefaultApp;比较容易理解,useRecoilState、useRecoilValue的基本概念可以参考官方文档另外,选择器也可以是异步的,例如:get:async({get})=>{constcountStateValue=get(countState);constresponse=awaitnewPromise((resolve)=>setTimeout(()=>resolve(countStateValue*2)),1000);返回响应;但是对于异步选择器,需要在RecoilRoot中加入一层Suspense:ReactDOM.render(Loading...
}>,document.getElementById("root"));ReduxvsRecoil模型对比:Recoil建议原子足够小,这样每个叶子组件可以单独订阅,当数据发生变化时,可以达到O(1)级别的更新。Recoil作者DaveMcCabe在评论中提到:嗯,我知道在一个工具上,我们看到与使用Redux相比,速度提高了20倍左右。这是因为Redux是O(n),因为它必须询问每个连接的组件是否需要重新渲染,而我们可以是O(1)。useReducer等同于useState,因为它适用于特定的cOmponent及其所有后代,而不是与React树正交。Rocil可以实现O(1)更新,因为当原子数据发生变化时,只需要重新渲染订阅原子的组件。不过,在Redux中,我们也可以使用selector来达到同样的效果://selectorconsttaskSelector=(id)=>state.tasks[id];//componentcodeconsttask=useSelector(taskSelector(id));但是这里有个小问题是,当state改变的时候,taskSelector也会重新计算,但是我们可以使用createSelector来优化,例如:import{createSelector}from'reselect';constshopItemsSelector=state=>state.shop.items;constsubtotalSelector=createSelector(shopItemsSelector,items=>items.reduce((acc,item)=>acc+item.value,0))写到这里,是不是想说,就这些?说了这么多,Rocoil能做的,Redux也能做,那还需要什么?哈哈,这真是尴尬。但是,我认为这是模型的变化。Recoil鼓励每个状态足够小,以任何方式组合,并在最小范围内更新。至于redux,我们的习惯是将容器组件连接到store。至于子组件,向下传递一层也没关系。我觉得Recoil的设计可能非常注重性能问题,优化超大型应用的性能。目前后坐力还处于玩具阶段,需要解决的问题还有很多,但值得继续关注。最后,有兴趣的朋友可以看看,做个todo-list来体验一下。希望这篇文章能帮到你。如果文章中有任何错误,请指正。如果您觉得该内容对您很有启发,您可以点击“在看”,让更多人看到该内容。关注公众号《前端e进阶》掌握前端面试重点难点,公众号后台回复“加群”与小伙伴聊技术。参考http://react.html.cn/docs/context.html#reactcreatecontexthttps://recoiljs.org/docs/basic-tutorial/atomshttps://www.emgoto.com/react-state-management/https:///medium.com/better-programming/recoil-a-new-state-management-library-moving-beyond-redux-and-the-context-api-63794c11b3a5