react的状态管理(react状态管理工具有哪些)
一、react状态管理核心概念
redux和mobx都是状态管理的库,与react本身并无关系,通过react-redux、mobx-react作为桥梁来结合react。
状态管理的主线:
创建state
注入state
state的变化触发ui的更新
1、redux + react-redux
redux
// redux // 创建 store = createStore(reducer); //方法 store.dispatch(action) store.getState() store.subscribe(listener) combineReducers bindActionCreators(actionCreator,dispatch) 异步中间件 redux-thunk复制代码
react-redux
// react-redux connect(mapStateToProps, mapDispatchToProps)(App) Provider + context // hook版本 useSelector useDispatch复制代码
2、mobx + mobx-react
mobx-react依赖于mobx-react-lite。后者是应用于函数组件的mobx库,也可以单独使用,如果只用hook版本的接口,可以考虑只引入mobx-react-lite。mobx-react既支持class组件,又支持函数组件。 mobx
observable reaction autorun computed action flow复制代码
mobx-react
observer inject Provider + context // hook版本 useLocalObservable //useLocalStore 已宣告废弃中,直接学习useLocalObservable Observer复制代码
3、安装流程
yarn create react-app mobx-redux cd mobx-redux yarn add redux react-redux mobx mobx-react -S yarn start复制代码
二、redux的原理和应用
1、原理
redux推崇单向数据流,状态只能由store派发,ui展示,不能反过来,状态的更新只能通过action=>reducer触发,并替换原先的状态,而非修改原先的状态。
状态管理的主线:创建state、注入state、ui与state同步。
2、使用redux实现这一过程
1、创建state
store.js
// store.js import {createStore} from 'redux' // store是状态的容器,包含了状态、修改状态的方法,监听状态改变的方法 const initialState = {count:0}; const reducer = function(state=initialState,action){ switch (action.type){ case 'ADD':{ return {count:state.count + 1} } default: return state; } } const store = createStore(reducer) export default store;复制代码
2、注入state
import React, {Component} from 'react'; import store from './store'; export default calss App extends Component { onAdd = () => { store.dispatch({ type: 'ADD' }) }; render(){ return ( <div className='App'> App-{store.getState().count} <button onClick={this.onAdd}>增加</button> </div> ) } }复制代码
此时界面上的数据还是不能更新的,接下来:
3、ui与state同步
import React, {Component} from 'react'; import store from './store'; export default calss App extends Component { constructor(){ super(); store.subscribe(()=>{ this.forceUpdate() //组件的强制刷新方法 }) } }复制代码
现在页面数据更新了,下面我们来看看redux的部分源码来了解下如何实现的更新。
4、实现redux源码中的createStore
function createStore(reducer){ let state; const listeners = []; //订阅事件的数组 const getState = () =>{ return state; }; const dispatch = action =>{ state = reducer(state,action); listeners.forEach(listener => listener()); }; const subscribe = listener =>{ if(!listeners.includes(listener)){ listeners.push(listener) } return function unsubscribe(){ listeners = listeners.filter(l=>l!==listener) } }; // 执行一次业务中不存在的type,目的是初始化state dispatch({type:'@@redux-init@@'}) return { getState, dispatch, subscribe } }复制代码
5、实际业务中的应用-connect
上述仅是示例,在实际应用中我们不会将store引入到业务组件中,而是借助react-redux的connect方法,将组件需要的状态注入到props中:
App.js中
import {connect} from 'react-redux' // 这两个映射用来生成注入组件的props const mapStateToProps = state => ({ count: state.count }) const mapDispatchToProps = dispatch =>({ dispatch, add:()=>dispatch({ type:'ADD' }) }) export default connect(mapStateToProps, mapDispatchToProps)(App);复制代码
react的context
context的使用方式很灵活,可以使用Provider/Consumer,也可以通过contextTypes/getChildContext,useContext等方式;
import {createContext, useContext, useState} from 'react'; const Context = createContext(); // 父组件通过Context.Provider传递数据到下级、后代组件 export default function Parent(){ const [count, setCount] = useState(0); const value = { count, increment(){ setCount(c => c+1) }, decrement(){ setCount(c => c-1) } } return ( <Context.Provider value={value}> <Sub /> </Context.Provider> ) } // 子组件通过context拿到上层数据 function Sub(){ const ctx = useContext(Context); return ( <div> {ctx.count} <button onClick={ctx.increment}>增加</button> <button onClick={ctx.decrement}>减少</button> </div> ) }复制代码
模拟实现一个connect
import React,{Component} from 'react'; import PropTypes from 'prop-types';//类型判断工具,只在开发环境有用 export function connect(mapStateToProps, mapDispatchToProps){ return function (WrappedComponent){ static contextTypes = { store:PropTypes.object } componentDidMount(){ // 从context获取store并订阅更新 this.context.subscribe(this.forceUpdate.bind(this)); } render(){ return ( <WrappedComponent //传入该组件的props,需要由connect这个高阶组件原样传回原组件 {...this.props} //根据mapStateToProps把state挂到this.props上 {...mapStateToProps(this.context.store.getState())} //根据mapDispatchToProps把state挂到this.props上 {...mapDispatchToProps(this.context.store.getState())} /> ) } } return Connect; }复制代码
connect导出的组件都是provider的后代组件,因此能够取到从provider传递的store;
整条链路如下:
初始化:
createStore生成全局的store
Provider转入store到项目根节点
connect通过context api拿到store并映射为被包装组件的props,同时把connect组件的更新方法注册到store listeners;
更新:
被包装的组件调用props中的action,即dispatch=>reducer
reducer生成新的state,同时遍历执行上一步采集的listeners;
connect基于变化的状态强制更新,包括子组件都会受到影响
6、异步中间件redux-thunk
what?
它是一个applyMiddleware(中间件)
作用
一个action中进行多次dispatch
一个action中做其他任何函数能做的事情
使用
// store.js import { createStore, applyMiddleware, combineReducers} from 'redux'; import thunk from 'redux-thunk'; // 如果只有一个reducer const store = createStore(reducer, applyMiddleware(thunk)); // 如果有多个reducer,我们会维护在不同的模块,然后集中到这里合并为一个reducer: // 借助combineReducers,同时下⽂ mapStateToProps 及 useSelector的第⼀个参数,也将相应地变成⼀个 { home, about } 对象 import home from '@/home/reducer'; import about from '@/about/reducer'; const reducer = combineReducers({ home, aboutState:about }) const store = createStore(reducer,applyMiddleware(thunk));复制代码
7、redux使用useSelector和useDispatch支持hook方式的状态管理
import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; export default function App(){ const number = useSelector(state=>state.number); // 如果上文使用了combineReducers,这里应该写成 // const number = useSelector(state=>state.home.number) const dispatch = useDispatch(); const onDecrement = ()=>{ dispatch({ type:'DECREMENT', payload:2 }) } return <button onClick={onDecrement}>{number}</button> }复制代码
三、mobx
1、概念
mobx推崇数据响应式,状态的mutable(可变的)。即状态创建后,后续都在修改这个状态,基于代理拦截数据的stter,从而触发副作用(在react中,包括render也可认为是mobx的副作用回调)。
注意事项:
在 react 中使用 mobx 的时候,你应该忘记 react 自带的组件更新方式,时刻牢记这句话,否则使用 mobx 将 失去价值,甚至引入 bug!
不要随意解构或使用基本类型的变量代替代理对象的属性,下文将演示这样导致的问题。
出于优化的目的,列表的每一项尽量封装为组件,这样 mobx 将会尽情出体现它的速度!
2、原则
mobx支持单向数据流,也就是动作改变状态,而状态的改变会更新所有受影响的视图。 action——>state——>views
3、常用api示例
1. observable: 创建状态,将值转为可响应的
2. reaction: 更新状态时的操作,第二个参数首次不执行
import {observable, reaction} from 'mobx' // 1、创建state const data = observable({value:0}); const dispatch = reaction( () => data.value, (cur,prev)=>{ console.log(cur,prev); if(cur>3){ dispose(); alert('不再追踪data') } } ); export default function Mobx(){ const onChange=()=>{ data.value++; } return ( // 2、注入state <button>改变data{data.value}</button> ) }复制代码
回顾状态管理的主线,第三步中的ui与state同步还没实现? 这里需要引入一个新的observer函数
import { observer } from 'mobx-react'; export default observer(function Mobx(){ const value = data.value; return ( <button onclick={onChange}>改变data {value}</button> ) });复制代码
import React, { Component } from 'react'; import { makeAutoObservable } from 'mobx'; import { observer } from 'mobx-react'; // 1、创建state class State { constructor(){ // 这是mobx6.x的方式,可以代替下面所有的声明 makeAutoObservable(this); // this.value = observable.box(0); // this.data = observable({}); } value = 0; onChange = ()=>{ this.value++; } } class Mbox extends Component { // 2、注入state state = new State() render(){ return ( <button onclick={onChange}>改变data {value}</button> ) } } // 3、state与ui同步 export default observer(Mobx)复制代码
3. action
动作,只对修改状态的函数使用动作,以允许mobx跟踪它们的调用。
action.bound,可以用来自动地将动作绑定到目标对象。绑定的this永远是正确的。(不能和箭头函数一起使用,箭头函数已经是绑定过的并且不能重新绑定)
示例:class + mobx
import React, { Component } from 'react'; import { makeObservable, action, observable } from 'mobx'; import { observer } from 'mobx-react' // 1.创建state class State { constructor(){ // 这里不是自动处理,而是人为控制那些数据具有响应能力,哪些方法是修改状态的 makeObservable(this,{ value:observable, //onChangeValue不是属性声明的箭头函数,所以应当绑定state实例 onChangeValue: action.bound, }) } count = 0 value = 0 onChangeValue(){ this.value++; } onChangeCount(){ this.count++; } } @observer //2、state与ui同步 class Mobx extends Component { state = new State() render(){ return ( <> <button onClick={this.state.onChangeValue}> 改变value {this.state.value} </button> <button onClick={this.state.onChangeCount}> 改变count {this.state.count} </button> </> ) } } export default Mobx;复制代码
4. makeObservable
定义了响应能力的属性和方法。 上述例子中count不是响应式的,所以改变count,页面上不会有变化。 对比makeAutoObservable会更清晰操作哪些需要响应,哪些不需要响应,从而达到节约性能的目的。
5. makeAutoObservable
自动转化 target
对象的属性和方法
6. flow
处理异步action的方法。 示例:实现请求的控制
import React,{Component} from 'react'; import {makeOberservable, action, observable,flow,computed} from 'mobx'; import {observer} from 'mobx-react'; // 模拟一个请求接口 const api = (name='')=>new Promise(resolve=>{ setTimeout(()=>{ const length = Math.cell(Math.random()*10); resolve({ code:200, data:Array(length).fill(0).map((_,index)=>({name:name +Math.random(),id:''})) }) },1000) }) class State { constructor(){ // 人为控制数据是否是响应式 makeObservable(this,{ list: observable, loading:observable, assign:action.bound, overflow: computed //衍⽣的属性只能访问,不要直接修改 }) } list = [] loading=false get overflow (){ return this.list.length >30 } // flow来处理异步action的方法 onFetch = flow(function *(string){ this.loading = true const {data} = yield api(string) this.list = data; this.loading = false }) } export default @observer class Mobx extends Component { state = new State() render(){ if(this.state.loading){ return <p>loading...</p> } return ( <> <div> { this.state.list.map(item=>( <p key={item.id}>{item.name}</p> )) } </div> <button onClick={()=>this.state.onFetch('响应')}>获取数据</button> </> ) } }复制代码
7. autorun
自动执行回调
dispose = autorun(()=>{ if(this.state.list.length > 5){ .... } })复制代码
8. functional+mobx
函数式组件的更新相当于重新调用组件自身(只有render部分) 那么怎么在实例化后的状态不会因为更新而被重置呢? 使用useLocalObservable创建响应式数据
import React from 'react'; import { useLocalObservable, observer } from 'mobx-react'; export default observer(function FunctionalMobx() { const state = useLocalObservable(() => ({ list: [], get overflow() { return this.list.length > 30; }, loading: false, async onFetch(string) { // 内部的 this 已经默认绑定到了 state this.loading = true; const { data } = await api(string); this.list = data; this.loading = false; } })); if (state.loading) { return <p>loading...</p> } return ( <> <div> { this.state.list.map(item=>( <p key={item.id}>{item.name}</p> )) } </div> <button onClick={()=>this.state.onFetch('响应')}>获取数据</button> </> ) })复制代码
9. Observer
用于追踪页面更新与state使用情况的部分,只会更新包裹的部分组件
import React from 'react'; import {useLocalObservable, Observer} from 'mobx-react'; // 函数组件不再被observer包裹 export default function FunctionalMobx(){ const state = useLocalObservable(()=>({ ... })) return ( // 使用Observer包裹[使用了响应式数据]的jsx <Observer> {()=>{ if (state.loading) { return <p>loading...</p> } return ( <> <div> { this.state.list.map(item=>( <p key={item.id}>{item.name}</p> )) } </div> <button onClick={()=>this.state.onFetch('响应')}>获取数据</button> </> ) }} </Observer> ) }复制代码
四、如何在全局状态共享中使用mobx
import React from 'react'; import { render } from 'react-dom'; import { Provider } from 'mobx-react'; import App from './App'; import store from './store'; render( <Provider {...store}> <App /> </Provider>, document.getElementById('root') )复制代码
store.js
class Home { constructor(){ makeObservable(this,{ data:observable, onChange:action.bound }) } data = 'home' onChange(data){ this.data = data; } } class About { constructor(){ makeObservable(this,{ value:observable, onChange:action.bound }) } value = 'About' onChange(value){ this.value = value; } } export default { home: new Home(), about:new About() }复制代码
下级组件使用
import React from 'react'; import { inject, Observer } from 'mobx-react'; function MobxReact ({home,about}){ return ( <h3> <h2>mobx-react</h2> <button onClick={()=> home.onChange(Math.random())}> home: <Observer>{()=> home.data}</Observer> </button> <br /> <button onClick={()=> home.onChange(Math.random())}> about: <Observer>{()=> about.value}</Observer> </button> </h3> ) } export default inject('home','about')(MobxReact);
作者:青咕咕
链接:https://juejin.cn/post/7046958351586394119