阅读 209

react的状态管理(react状态管理工具有哪些)

一、react状态管理核心概念

  • redux和mobx都是状态管理的库,与react本身并无关系,通过react-redux、mobx-react作为桥梁来结合react。

  • 状态管理的主线:

  1. 创建state

  2. 注入state

  3. 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;

  • 整条链路如下:

初始化:

  1. createStore生成全局的store

  2. Provider转入store到项目根节点

  3. connect通过context api拿到store并映射为被包装组件的props,同时把connect组件的更新方法注册到store listeners;

更新:

  1. 被包装的组件调用props中的action,即dispatch=>reducer

  2. reducer生成新的state,同时遍历执行上一步采集的listeners;

  3. connect基于变化的状态强制更新,包括子组件都会受到影响

6、异步中间件redux-thunk
  1. what?

它是一个applyMiddleware(中间件)

  1. 作用

  • 一个action中进行多次dispatch

  • 一个action中做其他任何函数能做的事情

  1. 使用

// 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的副作用回调)。

  • 注意事项:

  1. 在 react 中使用 mobx 的时候,你应该忘记 react 自带的组件更新方式,时刻牢记这句话,否则使用 mobx 将 失去价值,甚至引入 bug!

  2. 不要随意解构或使用基本类型的变量代替代理对象的属性,下文将演示这样导致的问题。

  3. 出于优化的目的,列表的每一项尽量封装为组件,这样 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


文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐