react怎么管理状态
导读:本文共12531字符,通常情况下阅读需要42分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要: react管理状态的工具:1、利用hooks进行状态管理;2、利用Redux进行状态管理,这种方式的配套工具比较齐全,可以自定义各种中间件;3、利用Mobx进行状态管理,它通过透明的函数响应式编程使得状态管理变得简单和可扩展。本教程操作环境:Wind... ...
目录
(为您整理了一些要点),点击可以直达。
react管理状态的工具:1、利用hooks进行状态管理;2、利用Redux进行状态管理,这种方式的配套工具比较齐全,可以自定义各种中间件;3、利用Mobx进行状态管理,它通过透明的函数响应式编程使得状态管理变得简单和可扩展。
本教程操作环境:Windows7系统、react17.0.1版、Dell G3电脑。
什么是 "状态"?
jQuery 时代,JS 代码中混杂 DOM 结构,各个流程庞杂交织时,就形成面条式代码,当使用发布订阅模型时,调试会一团乱麻。
jQuery 是针对 "过程" 的命令式编程,而那么多命令,最终都是为了更新 UI 中的 "数据",为什么不直接去改数据呢?
北京 → 上海,把 city="北京" 变为 city="上海" 就行。不管飞机火车步行抛锚,也不管路上会不会遇到王宝强,
现代前端框架的意义,就是问题解决思路的革新,把对 "过程" 的各种命令,变为了对 "状态" 的描述。
什么是状态?状态就是 UI 中的动态数据。
React 中的状态
2013 年 5 月 React 诞生。但 2015 年之前,大概都是 jQuery 的天下。2015 年 3 月 React 0.13.0 发布,带来了 class 组件写法。
在 React class 组件时代,状态就是 this.state,使用 this.setState 更新。
为避免一团乱麻,React 引入了 "组件" 和 "单向数据流" 的理念。有了状态与组件,自然就有了状态在组件间的传递,一般称为 "通信"。
父子通信较简单,而深层级、远距离组件的通信,则依赖于 "状态提升" + props 层层传递。
于是,React 引入了 Context,一个用于解决组件 "跨级" 通信的官方方案。
但 Context 其实相当于 "状态提升",并没有额外的性能优化,且写起来比较啰嗦。
为优化性能,一般会添加多个 Context,写起来就更啰嗦。在项目没那么复杂时,还不如层层传递简单。
什么是 "状态管理"?
实用主义来说,"状态管理" 就是为了解决组件间的 "跨级" 通信。
当然,在使用状态管理库时,其会带来一些衍生的思维模式,比如如何组织 state,如何拆分公共逻辑、业务逻辑、组件逻辑等,但归根结底,这些都不是核心缘由。
核心就是为了解决实际问题 —— 为了通信。其它的各种概念与哲学,都不是必要的。
Context 没那么好用,React 官方也没什么最佳实践,于是一个个社区库就诞生了。
react中的状态管理方式
目前比较常用的状态管理方式有hooks、redux、mobx三种,下面我将详细介绍一下这三类的使用方法以及分析各自的优缺点,以供各位进行参考。
Hooks状态管理
用hooks进行状态管理主要有两种方式:
useContext+useReducer
useState+useEffect
useContext+useReducer
使用方法
1.创建store和reducer以及全局context
src/store/reducer.ts
importReactfrom"react";//初始状态exportconststate={count:0,name:"ry",};//reducer用于修改状态exportconstreducer=(state,action)=>{const{type,payload}=action;switch(type){case"ModifyCount":return{...state,count:payload,};case"ModifyName":return{...state,name:payload,};default:{returnstate;}}};exportconstGlobalContext=React.createContext(null);
2.根组件通过 Provider 注入 context
src/App.tsx
importReact,{useReducer}from"react";import'./index.less'import{stateasinitState,reducer,GlobalContext}from'./store/reducer'importCountfrom'./components/Count'importNamefrom'./components/Name'exportdefaultfunction(){const[state,dispatch]=useReducer(reducer,initState);return(<div><GlobalContext.Providervalue={{state,dispatch}}><Count/><Name/></GlobalContext.Provider></div>)}
3.在组件中使用
src/components/Count/index.tsx
import{GlobalContext}from"@/store/reducer";importReact,{FC,useContext}from"react";constCount:FC=()=>{constctx=useContext(GlobalContext)return(<div><p>count:{ctx.state.count}</p><buttononClick={()=>ctx.dispatch({type:"ModifyCount",payload:ctx.state.count+1})}>+1</button></div>);};exportdefaultCount;
src/components/Name/index.tsx
import{GlobalContext}from"@/store/reducer";importReact,{FC,useContext}from"react";constName:FC=()=>{constctx=useContext(GlobalContext)console.log("NameRerendered")return(<div><p>name:{ctx.state.name}</p></div>);};exportdefaultName;
useState+useEffect
使用方法
1.创建state和reducer
src/global-states.ts
//初始stateletglobalState:GlobalStates={count:0,name:'ry'}//reducerexportconstmodifyGlobalStates=(operation:GlobalStatesModificationType,payload:any)=>{switch(operation){caseGlobalStatesModificationType.MODIFY_COUNT:globalState=Object.assign({},globalState,{count:payload})breakcaseGlobalStatesModificationType.MODIFY_NAME:globalState=Object.assign({},globalState,{name:payload})break}broadcast()}
src/global-states.type.ts
exportinterfaceGlobalStates{count:number;name:string;}exportenumGlobalStatesModificationType{MODIFY_COUNT,MODIFY_NAME}
2.写一个发布订阅模式,让组件订阅globalState
src/global-states.ts
import{useState,useEffect}from'react'import{GlobalStates,GlobalStatesModificationType}from'./global-states.type'letlisteners=[]letglobalState:GlobalStates={count:0,name:'ry'}//发布,所有订阅者收到消息,执行setState重新渲染constbroadcast=()=>{listeners.forEach((listener)=>{listener(globalState)})}exportconstmodifyGlobalStates=(operation:GlobalStatesModificationType,payload:any)=>{switch(operation){caseGlobalStatesModificationType.MODIFY_COUNT:globalState=Object.assign({},globalState,{count:payload})breakcaseGlobalStatesModificationType.MODIFY_NAME:globalState=Object.assign({},globalState,{name:payload})break}//状态改变即发布broadcast()}//useEffect+useState实现发布订阅exportconstuseGlobalStates=()=>{const[value,newListener]=useState(globalState)useEffect(()=>{//newListener是新的订阅者listeners.push(newListener)//组件卸载取消订阅return()=>{listeners=listeners.filter((listener)=>listener!==newListener)}})returnvalue}
3.组件中使用
src/App.tsx
importReactfrom'react'import'./index.less'importCountfrom'./components/Count'importNamefrom'./components/Name'exportdefaultfunction(){return(<div><Count/><Name/></div>)}
src/components/Count/index.tsx
importReact,{FC}from'react'import{useGlobalStates,modifyGlobalStates}from'@/store/global-states'import{GlobalStatesModificationType}from'@/store/global-states.type'constCount:FC=()=>{//调用useGlobalStates()即订阅globalStates()const{count}=useGlobalStates()return(<div><p>count:{count}</p><buttononClick={()=>modifyGlobalStates(GlobalStatesModificationType.MODIFY_COUNT,count+1)}>+1</button></div>)}exportdefaultCount
src/components/Name/index.tsx
importReact,{FC}from'react'import{useGlobalStates}from'@/store/global-states'constCount:FC=()=>{const{name}=useGlobalStates()console.log('NameRerendered')return(<div><p>name:{name}</p></div>)}exportdefaultCount
优缺点分析
由于以上两种都是采用hooks进行状态管理,这里统一进行分析
优点
代码比较简洁,如果你的项目比较简单,只有少部分状态需要提升到全局,大部分组件依旧通过本地状态来进行管理。这时,使用 hookst进行状态管理就挺不错的。杀鸡焉用牛刀。
缺点
两种hooks管理方式都有一个很明显的缺点,会产生大量的无效rerender,如上例中的Count和Name组件,当state.count改变后,Name组件也会rerender,尽管他没有使用到state.count。这在大型项目中无疑是效率比较低的。
Redux状态管理
使用方法:
1.引入redux
yarnaddreduxreact-redux@types/react-reduxredux-thunk
2.新建reducer
在src/store/reducers文件夹下新建addReducer.ts(可建立多个reducer)
import*astypesfrom'../action.types'import{AnyAction}from'redux'//定义参数接口exportinterfaceAddState{count:numbername:string}//初始化stateletinitialState:AddState={count:0,name:'ry'}//返回一个reducerexportdefault(state:AddState=initialState,action:AnyAction):AddState=>{switch(action.type){casetypes.ADD:return{...state,count:state.count+action.payload}default:returnstate}}
在src/stores文件夹下新建action.types.ts
主要用于声明action类型
exportconstADD='ADD'exportconstDELETE='DELETE'
3.合并reducer
在src/store/reducers文件夹下新建index.ts
import{combineReducers,ReducersMapObject,AnyAction,Reducer}from'redux'importaddReducer,{AddState}from'./addReducer'//如有多个reducer则合并reducers,模块化exportinterfaceCombinedState{addReducer:AddState}constreducers:ReducersMapObject<CombinedState,AnyAction>={addReducer}constreducer:Reducer<CombinedState,AnyAction>=combineReducers(reducers)exportdefaultreducer
3.创建store
在src/stores文件夹下新建index.ts
import{createStore,applyMiddleware,StoreEnhancer,StoreEnhancerStoreCreator,Store}from'redux'importthunkfrom'redux-thunk'importreducerfrom'./reducers'//生成store增强器conststoreEnhancer:StoreEnhancer=applyMiddleware(thunk)conststoreEnhancerStoreCreator:StoreEnhancerStoreCreator=storeEnhancer(createStore)conststore:Store=storeEnhancerStoreCreator(reducer)exportdefaultstore
4.根组件通过 Provider 注入 store
src/index.tsx(用provider将App.tsx包起来)
importReactfrom'react'importReactDOMfrom'react-dom'importAppfrom'./App'import{Provider}from'react-redux'importstorefrom'./store'ReactDOM.render(<Providerstore={store}><App/></Provider>,document.getElementById('root'))
5.在组件中使用
src/somponents/Count/index.tsx
importReact,{FC}from'react'import{connect}from'react-redux'import{Dispatch}from'redux'import{AddState}from'src/store/reducers/addReducer'import{CombinedState}from'src/store/reducers'import*astypesfrom'@/store/action.types'//声明参数接口interfaceProps{count:numberadd:(num:number)=>void}//ReturnType获取函数返回值类型,&交叉类型(用于多类型合并)//typeProps=ReturnType<typeofmapStateToProps>&ReturnType<typeofmapDispatchToProps>constCount:FC<Props>=(props)=>{const{count,add}=propsreturn(<div><p>count:{count}</p><buttononClick={()=>add(5)}>addCount</button></div>)}//这里相当于自己手动做了映射,只有这里映射到的属性变化,组件才会rerenderconstmapStateToProps=(state:CombinedState)=>({count:state.addReducer.count})constmapDispatchToProps=(dispatch:Dispatch)=>{return{add(num:number=1){//payload为参数dispatch({type:types.ADD,payload:num})}}}exportdefaultconnect(mapStateToProps,mapDispatchToProps)(Count)
src/somponents/Name/index.tsx
importReact,{FC}from'react'import{connect}from'react-redux'import{Dispatch}from'redux'import{AddState}from'src/store/reducers/addReducer'import{CombinedState}from'src/store/reducers'import*astypesfrom'@/store/action.types'//声明参数接口interfaceProps{name:string}constName:FC<Props>=(props)=>{const{name}=propsconsole.log('NameRerendered')return(<div><p>name:{name}</p></div>)}//name变化组件才会rerenderconstmapStateToProps=(state:CombinedState)=>({name:state.addReducer.name})//addReducer内任意属性变化组件都会rerender//constmapStateToProps=(state:CombinedState)=>state.addReducerexportdefaultconnect(mapStateToProps)(Name)
优缺点分析
优点
组件会订阅store中具体的某个属性【mapStateToProps手动完成】,只要当属性变化时,组件才会rerender,渲染效率较高
流程规范,按照官方推荐的规范和结合团队风格打造一套属于自己的流程。
配套工具比较齐全redux-thunk支持异步,redux-devtools支持调试
可以自定义各种中间件
缺点
state+action+reducer的方式不太好理解,不太直观
非常啰嗦,为了一个功能又要写reducer又要写action,还要写一个文件定义actionType,显得很麻烦
使用体感非常差,每个用到全局状态的组件都得写一个mapStateToProps和mapDispatchToProps,然后用connect包一层,我就简单用个状态而已,咋就这么复杂呢
当然还有一堆的引入文件,100行的代码用了redux可以变成120行,不过换个角度来说这也算增加了自己的代码量
好像除了复杂也没什么缺点了
Mobx状态管理
MobX 是一个经过战火洗礼的库,它通过透明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态管理变得简单和可扩展。
常规使用(mobx-react)
使用方法
1.引入mobx
yarnaddmobxmobx-react-D
2.创建store
在/src/store目录下创建你要用到的store(在这里使用多个store进行演示)
例如:
store1.ts
import{observable,action,makeObservable}from'mobx'classStore1{constructor(){makeObservable(this)//mobx6.0之后必须要加上这一句}@observablecount=0@observablename='ry'@actionaddCount=()=>{this.count+=1}}conststore1=newStore1()exportdefaultstore1
store2.ts
这里使用 makeAutoObservable代替了makeObservable,这样就不用对每个state和action进行修饰了(两个方法都可,自行选择)
import{makeAutoObservable}from'mobx'classStore2{constructor(){//mobx6.0之后必须要加上这一句makeAutoObservable(this)}time=11111111110}conststore2=newStore2()exportdefaultstore2
3.导出store
src/store/index.ts
importstore1from'./store1'importstore2from'./store2'exportconststore={store1,store2}
4.根组件通过 Provider 注入 store
src/index.tsx(用provider将App.tsx包起来)
importReactfrom'react'importReactDOMfrom'react-dom'importAppfrom'./App'importstorefrom'./store'import{Provider}from'mobx-react'ReactDOM.render(<Provider{...store}><App/></Provider>,document.getElementById('root'))
5.在组件中使用
src/somponents/Count/index.tsx
importReact,{FC}from'react'import{observer,inject}from'mobx-react'//类组件用装饰器注入,方法如下//@inject('store1')//@observerinterfaceProps{store1?:any}constCount:FC<Props>=(props)=>{const{count,addCount}=props.store1return(<div><p>count:{count}</p><buttononClick={addCount}>addCount</button></div>)}//函数组件用Hoc,方法如下(本文统一使用函数组件)exportdefaultinject('store1')(observer(Count))
src/components/Name/index.tsx
importReact,{FC}from'react'import{observer,inject}from'mobx-react'interfaceProps{store1?:any}constName:FC<Props>=(props)=>{const{name}=props.store1console.log('NameRerendered')return(<div><p>name:{name}</p></div>)}//函数组件用Hoc,方法如下(本文统一使用函数组件)exportdefaultinject('store1')(observer(Name))
优缺点分析:
优点:
组件会自动订阅store中具体的某个属性,无需手动订阅噢!【下文会简单介绍下原理】只有当订阅的属性变化时,组件才会rerender,渲染效率较高
一个store即写state,也写action,这种方式便于理解,并且代码量也会少一些
缺点:
当我们选择的技术栈是React+Typescript+Mobx时,这种使用方式有一个非常明显的缺点,引入的store必须要在props的type或interface定义过后才能使用(会增加不少代码量),而且还必须指定这个store为可选的,否则会报错(因为父组件其实没有传递这个prop给子组件),这样做还可能会致使对store取值时,提示可能为undefined,虽然能够用“!”排除undefined,可是这种作法并不优雅。
最佳实践(mobx+hooks)
使用方法
1.引入mobx
同上
2.创建store
同上
3.导出store(结合useContext)
src/store/index.ts
importReactfrom'react'importstore1from'./store1'importstore2from'./store2'//导出store1exportconststoreContext1=React.createContext(store1)exportconstuseStore1=()=>React.useContext(storeContext1)//导出store2exportconststoreContext2=React.createContext(store2)exportconstuseStore2=()=>React.useContext(storeContext2)
4.在组件中使用
无需使用Provider注入根组件
src/somponents/Count/index.tsx
importReact,{FC}from'react'import{observer}from'mobx-react'import{useStore1}from'@/store/'//类组件可用装饰器,方法如下//@observerconstCount:FC=()=>{const{count,addCount}=useStore1()return(<div><p>count:{count}</p><buttononClick={addCount}>addCount</button></div>)}//函数组件用Hoc,方法如下(本文统一使用函数组件)exportdefaultobserver(Count)
src/components/Name/index.tsx
importReact,{FC}from'react'import{observer}from'mobx-react'import{useStore1}from'@/store/'constName:FC=()=>{const{name}=useStore1()console.log('NameRerendered')return(<div><p>name:{name}</p></div>)}exportdefaultobserver(Name)
优缺点分析:
优点:
学习成本少,基础知识非常简单,跟 Vue 一样的核心原理,响应式编程。
一个store即写state,也写action,这种方式便于理解
组件会自动订阅store中具体的某个属性,只要当属性变化时,组件才会rerender,渲染效率较高
成功避免了上一种使用方式的缺点,不用对使用的store进行interface或type声明!
内置异步action操作方式
代码量真的很少,使用很简单有没有,强烈推荐!
缺点:
过于自由:Mobx提供的约定及模版代码很少,这导致开发代码编写很自由,如果不做一些约定,比较容易导致团队代码风格不统一,团队建议启用严格模式!
使用方式过于简单
Mobx自动订阅实现原理
基本概念
Observable//被观察者,状态Observer//观察者,组件Reaction//响应,是一类的特殊的Derivation,可以注册响应函数,使之在条件满足时自动执行。
建立依赖
我们给组件包的一层observer实现了这个功能
exportdefaultobserver(Name)
组件每次mount和update时都会执行一遍useObserver函数,useObserver函数中通过reaction.track进行依赖收集,将该组件加到该Observable变量的依赖中(bindDependencies)。
//fn=function(){returnbaseComponent(props,ref);exportfunctionuseObserver(fn,baseComponentName){...varrendering;varexception;reaction.track(function(){try{rendering=fn();}catch(e){exception=e;}});if(exception){throwexception;//re-throwanyexceptionscaughtduringrendering}returnrendering;}
reaction.track()
_proto.track=functiontrack(fn){//开始收集startBatch();varresult=trackDerivedFunction(this,fn,undefined);//结束收集endBatch();};
reaction.track里面的核心内容是trackDerivedFunction
functiontrackDerivedFunction<T>(derivation:IDerivation,f:()=>T,context:any){ ...letresult//执行回调f,触发了变量(即组件的参数)的get,从而获取dep【收集依赖】if(globalState.disableErrorBoundaries===true){result=f.call(context)}else{try{result=f.call(context)}catch(e){result=newCaughtException(e)}}globalState.trackingDerivation=prevTracking//给observable绑定derivationbindDependencies(derivation)...returnresult}
触发依赖
Observable(被观察者,状态)修改后,会调用它的set方法,然后再依次执行该Observable之前收集的依赖函数,触发rerender。
组件更新
用组件更新来简单阐述总结一下:mobx的执行原理。
observer这个装饰器(也可以是Hoc),对React组件的render方法进行track。
将render方法,加入到各个observable的依赖中。当observable发生变化,track方法就会执行。
track中,还是先进行依赖收集,调用forceUpdate去更新组件,然后结束依赖收集。
每次都进行依赖收集的原因是,每次执行依赖可能会发生变化。
</div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
react怎么管理状态的详细内容,希望对您有所帮助,信息来源于网络。