(三)React 状态管理

一、简介

参考资料:

  1. redux中文官网教程
  2. redux中文文档
  3. react-redux中文文档
  4. 理解react-thunk
  5. mobx官网
  6. mobx中文文档

涉及数据共享的方案,通常有三条规则:

  • 组件之外申明要共享的状态(包括安全合法修改状态的方法)
  • 将该状态注入到UI组件
  • 让组件的更新与状态的变化同步

常用的两个状态管理库:

  • redux:基于状态管理与共享而生的一套单向数据流方案,独立于react的js库,可通过react-redux桥梁应用于react,推崇思想immutable(不可变的),便于时间旅行(每次产生新的状态,不修改原状态),MVC
    • redux
      • store = createStore(reducer, [preloadedState], [enhancer])
      • store.dispatch(action)
      • store.getState()
      • store.subscribe(listener)
      • combineReducers
      • bindActionCreators(actionCreator, dispatch)
    • react-redux:链接redux和react的桥梁,将store注入业务组件中
      • connect(mapStateToProps, mapDispatchToProps)(ViewComponent)
      • Provider + context
    • redux-thunk:异步中间件
    • hook 版本
      • useSelector
      • useDispatch
  • mobx:基于defineProperty(v4及之前)或Proxy(v4之后)来实现对数据的劫持并相应动作的状态管理方案,可通过mobx-react桥梁应用于react,推崇思想:mutable(响应式的,直接修改状态),MVVM,时间旅行可借助mobx-state-tree库
    • mobx
      • configure({enforceActions: true})
      • observable
      • reaction
      • autorun
      • runInAction
      • computed
      • action
      • Flows
    • mobx-react:链接react和mobx
      • observer
      • inject
      • Provider + context
      • makeAutoObservable
    • mobx-state-tree:时间旅行,快照
    • hook 版本
      • useLocalObservable (useLocalStore 已经宣告废弃中,直接学习 useLocalObservable)
      • Observer

二、redux、react-redux框架

1、思想

  • Redux三大原则
    • 单一数据源:整个应用的state被存储在一颗object tree中
    • state时只读的
    • 使用纯函数来执行修改
  • Redux使用:单向数据流
    • state描述了应用程序在某个时间点的状态,UI基于该状态渲染
    • 当应用程序中发生某些事情时:
      • UI dispacth一个action
      • store调用reducer,随后根据发生的事情来更新state
      • store通知UI state发生了变化
    • UI基于新的state重新渲染
  • Redux有这几种类型的代码
    • Action是有type字段的纯对象,描述发生了什么
    • Reducer是纯函数,基于先前的state和action来计算新的state
    • 每当dispatch一个action后,store就会调用root reducer

2、使用

1、整体使用
/* 
1. 创建store,使用createStore(reducers)
2. 注入项目根容器,使用react-redux的provider
3. 分发到模块组件,使用react-redux的connect高阶函数
connect(mapStateToProps, mapDispatchToProps)(Container)
*/

//1. 创建store: store.js
import { createStore } from 'redux';
// store 是状态的容器,包含了状态、修改状态的⽅法,监听状态改变的⽅法。
// reducers 会在独立的文件
const initialState = {list: [{id: 1}]};
function reducers(state = initialState, action){
    switch(action.type){
        case "ADD": {
            return {...state, list: [...state.list, action.payload]}
        }
        case "DELETE": {
            return {...state, list: state.list.filter(({id}) => {
                return id !== action.payload
            })}
        }
        default: return state;
    }
}
const store = createStore(reducers);  // 先忽略其他参数
export default store;

//2. 注入根容器: index.js
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
render(
  <Provider store={store}>
  	<App />
  </Provider>,
  document.getElementById('root')
);

//3.1 模块组件使用List.js
import React from 'react'
function List({ list, onAdd, onDel }) {
    return (
        <div>
            <button onClick={() => onAdd({ id: Math.random() })}>add</button>
            {list.map(({ id }) =>
                <div onClick={() => onDel(id)} key={id}>
                    {id}
                    <button onClick={() => onDel(id)}>del</button>
                </div>
            )}
        </div>
    )
}
export default List;

//3.2 模块组件使用ListContainer.js
import {connect} from 'react-redux'
import List from './list'
const mapStateToProps = (state)=> {
    return {
        list: state.list
    }
};
const mapDispatchToProps = (dispatch) => {
    return {
        onAdd: (list)=> {
            dispatch({type: "ADD", payload: list});
        },
        onDel: (id)=> {
            dispatch({type: "DELETE", payload: id});
        }
    };
};
export default connect(mapStateToProps, mapDispatchToProps)(List)
复制代码
2、异步中间件

不使用异步中间件,只能将异步操作方针UI组件中,不利于解耦

import React from 'react'
function List({ list, onAdd, onDel }) {
  	const removeRemote = id => {
      fetch('/api').then(()=>{
        onDel(id);
      }).catch(()=>{})
    }
    return (
        <div>
            <button onClick={() => onAdd({ id: Math.random() })}>add</button>
            {list.map(({ id }) =>
                <div onClick={() => onDel(id)} key={id}>
                    {id}
                    <button onClick={() => onDel(id)}>del</button>
                </div>
            )}
        </div>
    )
}
export default List;
复制代码

redux-thunk这个中间件可以使我们把这样的异步请求或者说复杂的逻辑可以放到action里面去处理。

  • redux的中间件指的是action和store之间,action只能是一个对象,所以action是一个对象直接派发给了store。
  • redux-thunk中间件,对store对dispatch方法做一个升级,之前这个dispatch方法只能接收一个对象,现在升级之后,就可以接收对象,也可以接收函数,从而实现异步执行dispatch
//1. 创建store: store.js
import { createStore, applyMiddleware} from 'redux';
import thunkMiddleware from 'redux-thunk'
function reducers(state = {list: [{id: 1}]}, action){
    switch(action.type){
        case "UPDATE": {
            return  {...state, list: state.list.map((item) => {
                return item.id === action.payload ? {id: 'update'} : item
            })}
        }
        default: return state;
    }
}
const store = createStore(reducers, applyMiddleware(thunkMiddleware));
export default store;

//2.1 UI组件使用List.js
import React from 'react'
function List({list, onUpdate}) {
    return (
        <div>
            <button onClick={() => onUpdate(1)}>update</button>
            {list.map(({ id }) => <div key={id}>{id}</div>)}
        </div>
    )
}
export default List;

//2.2 容器组件使用ListContainer.js
import {connect} from 'react-redux'
import List from './list'
const mapStateToProps = (state)=> {
    return {
        list: state.list
    }
};
const mapDispatchToProps = (dispatch) => {
    return {
        onUpdate: (id) => dispatch(onUpdate(id))
    };
};
const onUpdate = id => dispatch => {
    new Promise(resolve => {
        setTimeout(()=>{
            dispatch({
                type: 'UPDATE',
                payload: id
            })
        }, 2000)
    })
};
export default connect(mapStateToProps, mapDispatchToProps)(List)
复制代码

中间件格式:

function logger({getState}){
  //next为一个dispacth,action对象{type: xx, payload}
  return next => action => {
    console.log('before dispatch');
    const result = next(action);
    console.log('after dispatch');
    return result;
  }
}
createStore(reducers, applyMiddleware(logger));
复制代码
3、actionCreator整合

可使用bindActionCreators进行actionCreator的自动整合。把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch对每个 action creator 进行包装,以便可以直接调用它们。

//actions.js
export function addTodo(text) {
  return {
    type: 'ADD_TODO',
    text
  }
}
export function removeTodo(id) {
  return {
    type: 'REMOVE_TODO',
    id
  }
}

//容器组件
import * as actions from './actions'
import {bindActionCreators} from 'redux'
const mapDispatchToProps = (dispatch) => {
  ...bindActionCreators(actions, dispatch)
};
/* 等价于
const actions = {
	addTodo,
	removeTodo
}
const mapDispatchToProps = (dispatch) => {
  addTodo: payload => dispatch(actions.addTodo(payload))
  removeTodo: payload => dispatch(actions.removeTodo(payload))
};
*/
复制代码
4、模块拆分解耦合

React-Redux 将所有组件分成两大类:UI 组件和容器组件。使用connect方法,用于从 UI 组件生成容器组件,将UI组件和容器组件进行解耦拆分。

  • UI组件:只负责 UI 的呈现,不带有任何业务逻辑,没有状态state值的使用,所以的参数是通过this.props获取。
  • 容器组件:负责管理数据和业务逻辑,不负责 UI 的呈现,有业务逻辑,并且使用Redux提供的API

同时结合combineReducers的使用,将reducers进行拆分解耦

//reducers组合
import reducer1 from "../Views/list/list-reducer"
import {combineReducers} from "redux"
const reducer = combineReducers({
    reducer1: reducer1
})
export default reducer

//reducers使用
/* 
1、state中两个reducer会合并,合并后变为:
{
	reducer1: ...
	reducer2: ...
}
2、action会直接合并和使用,为了防止重名问题
2.1、使用命名空间:
	a.js中:export const GATHERING_INFO_ENTRY_SAGA = 'a/GATHERING_INFO_ENTRY_SAGA';
	b.js中:export const GATHERING_INFO_ENTRY_SAGA = 'b/GATHERING_INFO_ENTRY_SAGA';
2.2、使用Symbol对象
  a.js和b.js中:export const GATHERING_INFO_ENTRY_SAGA = Symbol('gatheringInfoEntrySaga');
*/
import {connect} from 'react-redux'
import List from './list'
const mapStateToProps = (state)=> {
    return {
        list: state.reducer1.list
    }
};
const mapDispatchToProps = (dispatch) => {
    return {
        onAdd: (list)=> {
            dispatch({type: "ADD", payload: list});
        },
        onDel: (id)=> {
            dispatch({type: "DELETE", payload: id});
        },
      	default: return state;
    };
};
export default connect(mapStateToProps, mapDispatchToProps)(List)
复制代码

3、原理解析

1、connect

使⽤ connect + mapStateToProps + mapDispatchToProps 来获取状态与⽅法的形式可以说是 react hooks 早先结合 redux 唯⼀的⽅式。

常用参数:connect([mapStateToProps], [mapDispatchToProps])(Container)

  • redux-react的高阶函数,返回一个注入参数的组件,用于store注入以及触发渲染更新
  • mapStateToProps:state到props的映射函数
    • [mapStateToProps(state, [ownProps]): stateProps] (Function)
  • mapDispatchToProps:dispatch到props的映射函数
    • [mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function)
import React, { useContext, useEffect, useState } from 'react';
const reduxContext = createContext();
export const connect = (mapStateToProps, mapDispatchToProps) =>
    Component => function Connect(props) {
        const store = useContext(reduxContext);
        const [, setCount] = useState(true);
        const forceUpdate = () => setCount(value => !value);
//每当store有更新,都将执行这里注册的forceUpdate方法,用来更新Connect组件,后代组件也会随之更新。
        useEffect(() => store.subscribe(forceUpdate), []);
        return (
            <Component
                // 传入该组件的 props,需要由 connect 这个高阶组件原样传回原组件
                { ...props }
                { ...mapStateToProps(store.getState()) }
                { ...mapDispatchToProps(store.dispatch) }
            />
        );
    }
复制代码
2、createStore

createStore(reducer, [preloadedState], [enhancer])

//核心逻辑
function createStore(reducers) {
    let state, listeners = [];
    const store = {
        dispatch(action) {
            state = reducers(state, action);
            //触发订阅事件,每当发起一个action的时候,即store发生变化,就执行一遍之前订阅过的事件
            listeners.forEach(listener => listener());
            return action; //可以不返回,有返回值的话允许我们多次调用
        },
        getState() {
            //闭包的形式保证 state 必须通过暴露的方法来更新
            return state;
        },
        subscribe(listener) {
            listeners.push(listener);
            //每当订阅一个事件的时候,随即返回注销该事件的方法
            return function unsubscribe() {
                    listeners = listeners.filter(cur => cur !== listener);
            };
        }
    }
    //主动调用,目的是初始化state,type是reducers中不存在的值,因此在首次执行会将initialState赋值给state
    store.dispatch({ type: '@@redux-init@@' });
    return store;
}
复制代码
3、redux-thunk

如果是function类型,就调用这个function(并传入 dispatch 和 getState 及 extraArgument 为参数),而不是任由让它到达 reducer,因为 reducer 是个纯函数,Redux 规定到达 reducer 的 action 必须是一个 plain object 类型

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
复制代码
4、bindActionCreators
const bindActionCreators = (actions, dispatch) => {
  Object.entries(actions).reduce((prev, [key, action]) => ({
    ...prev,
    [key]: (...args) => dispatch(action(...args))
  }), {})
}
复制代码
5、combineReducers
export function combineReducers(obj) {
  return function (state={}, action){
    Object.keys(obj).forEach((key) => {
      state[key] = obj[key](state[key], action)
    })
    return state;
  }
}
复制代码

三、mobx、mobx-react框架

1、思想

React 和 MobX 是一对强力组合。React 通过提供机制把应用状态转换为可渲染组件树并对其进行渲染。而MobX提供机制来存储和更新应用状态供 React 使用。

  • React 提供了优化UI渲染的机制, 这种机制就是通过使用虚拟DOM来减少昂贵的DOM变化的数量。
  • MobX 提供了优化应用状态与 React 组件同步的机制,这种机制就是使用响应式虚拟依赖状态图表,它只有在真正需要的时候才更新并且永远保持是最新的。

2、使用

1、整体流程
  • 创建状态:mobx 的状态基于对普通对象的封装(代理),状态的声明借助方法observable
//1、普通对象
import { observable } from 'mobx';
const object = observable({ value: 0 });
console.log(object.value);
// 基本类型的值包装
const count = observable.box(1);
console.log(count.get());

//2、集中包装到类属性中,用于数据解耦
class State {
    name = observable.box('张三')
    something = observable({ money: 123, age: 24 })
}
const state = new State();
state.name.get();
state.name.set('李四');
state.something.money = 0;

//3、使用装饰器,修饰类的属性或方法
class State {
    @observable name = '张三' 
    @observable something = { money: 123, age: 24 }
}
const state = new State();
state.name = '李四';
state.something = { text: '文本', score: 120 };
复制代码
  • 状态注入
//1. 全局组件直接挂载
class App extends React.Component {
    state = new State() //直接挂载至类上
    render() {
        return <span>{ this.state.name }</span>;
    } 
}

//2. 局部组件之间申明
//2.1 类组件:可使用装饰器
class App extends React.Component {
  @observable name = 'Jian'
    render() {
        return <span>{ this.name }</span>;
    }
}
//2.2 函数组件
import { useState } from 'react';
function App() {
    const [name] = useState(() => observable.box('Jian'));
    return <span>{ name.get() }</span>;
}
复制代码
  • 状态与更新同步:使用mobx-react

    • observer:observer小写对象,为高阶函数,用于组件装饰
    • Observer:Observer大写对象,为监测组件,DOM形式
import { observer, Observer } from 'mobx-react';
import { observable, action } from 'mobx';
configure({ enforceActions: true })
// 装饰 App 组件, 等价于export default observer(App);
@observer
class App extends React.Component {
  @observable name = 'Jian'
  
  //严格模式下,在action之外修改name将会报错
  //1. 无反应,name不修改
  onChange = (e) => {
        this.name = e.target.value;
  }
  //2. 写法1,使用action包裹
  onChange = action(e => {
        this.name = e.target.value;
  })
  //3. 写法2,使用装饰器
  @action
  onChange = e => {
        this.name = e.target.value;
  }
  //4. 写法3
  @action.bound
  onChange(e){
        this.name = e.target.value;
  }

  render() {
        return <>
            <span>{ this.name }</span>
            <input onChange={this.onChange} />
        </>;
  } 
}

//使用Observer组件写法
//将响应式数据放在<Observer>标签内进行监听,不会引起组件及父组件渲染
//后续执行只会执行<Observer>标签内的内容
import React, {useState} from 'react' //注意react也需要引入
import {observable} from 'mobx'
import {Observer, observer} from 'mobx-react'

class App extends React.Component{
  //等价于定义 data = observable({value: 1})
  @observable data = {value: 1} 
  render(){
    return <Observer>
      {
        () => (
            <div className="App">
                <p onClick={()=>{
                    this.data.value = Math.random()
                  }}>
                  mobx test
                </p>
                data: {this.data.value}
            </div>)
      }
    </Observer>
  }
}
export default App;
复制代码
2、异步写法
  • runInActions:runInAction(f) 实际上是 action(f)() 的语法糖。
  • flows:只能作为函数使用,不能作为装饰器使用,使用生成器
//异步使用action
class State {
  @observable data = {value: 1}
  value = 12
  onChange = () => {
    fetch('/api', {data: value}).then(
      action(data => {
        this.data = data;
      })
    )
  })
}

//使用runInAction
class State {
  @observable data = {value: 1}
  value = 12
  onChange = () => {
    fetch('/api', {data: value}).then(
      runInAction(data => {
        this.data = data;
      })
    )
  })
}

//flow使用
class State {
  @observable data = {value: 1}
  value = 12

  onChange = flow(function *(){
    const res = yield fetch('/api', {data: value})
    this.data = res
  })
}
复制代码
3、reaction

用于监听响应式数据进行处理

用法:reaction(() => data, (data, reaction) => { sideEffect }, options?)

//reaction使用
//第一个参数为函数,返回响应式的数据
//第二个函数为响应式数据修改后的回调,其入参为第一个参数的返回
//返回一个清理函数
class State {
  @observable data = {value: 1}
  @action onChange = () => {
    this.data.value = Math.random()
  }
}

@observer
class App extends React.Component{
  state = new State()
  //设置监听:data的变化时,通过数据响应调用回调
  dispose = reaction(
    () => this.state.data.value,
    value => fetch('/api', {data: value})
  )

  componentWillUnmount(){
    this.dispose()//取消监听
  }

  render(){
    return (
     	<div className="App">
            <p onClick={()=>{this.state.onChange()}}>
              mobx test
            </p>
      	    data: {this.state.data.value}
    	</div>)
  }
}
export default App;
复制代码
4、状态拆分与注入

通过类的封装实现状态拆分和整合,通过context的api以及inject实现全局状态共享使用

//1. 全局注入状态:index.js
import { createContext, useContext } from 'react';
import { render } from 'react-dom';
import { observable, action, computed, configure } from 'mobx';
import {Provider} from 'mobx-react'
import App from "./App";

configure({enforceActions: 'always'});
//封装User的状态和修改
class User {
  @observable name = ''
  @observable age = 18
  @observable school = 'Qinghua'
  @computed get detail() { //缓存数据
  	return this.name + this.age + this.school;
  }
  //将对象合并在类中
  @action onChange(obj) {
        Object.assign(this, obj);
  } 
}

//封装status的状态和修改
class Status {
   @observable running = false
   @observable eating = true
   @observable sleeping = false
   @action onChange(obj) {
       Object.assign(this, obj);
   } 
}

//封装status的状态和修改
class Message {
  constructor(){
    //mobx 5+版本api,将类的属性和方法自动设置为响应式
    makeAutoObservable(this);
  }
  @action onChange(obj) {
    Object.assign(this, obj);
  } 
}

// 将不同模块的状态集中为一个 store
const store = {
  user: new User(),
  status: new Status(),
  message: new Message()
};

//使用context的api注入
render(
  <Provider {...store}>
  <App />
  </Provider>,
  document.getElementById('root')
);


//2. 组件使用状态inject
import { inject } from 'mobx-react';

@inject(['user', 'status'])
export default class App extends React.Component {
    render() {
           console.log(this.props); // { user, status }
    } 
}
// 或这种使用方式
@inject(({ user }) => ({ user }))
export default class App extends React.Component {
    render() {
            console.log(this.props); // { user }
    } 
}
复制代码

4、原理

1、inject

将context注入到组件props中,便于后代组件消费store

const store = {
  user: new User(),
  status: new Status(),
  message: new Message()
};
const mobxContext = createContext();
//使用context的api注入
render(
  <mobxContext.Provider value={store}>
  <App />
  </mobxContext>,
  document.getElementById('root')
);

//高阶函数注入store
//使用:inject('user', 'status')(App)
//使用:inject(store => {user : store.user})(App)
function inject = (mapStoreToProps, ...rest) => 
    (Component) => {
    const StateComponent = props => {
        const store = useContext(mobxContext);
        let newProps;
        if(typeof mapStoreToProps === 'function'){
          newProps = mapStoreToProps(store);
        }else{
          newProps = [mapStoreToProps, ...rest].reduce(
            (prev, storeName) => ({
              ...prev,
              [storeName]: store[storeName]
            }), props)
        }
        return <Component { ...props } { ...newProps } />
    };
    return StateComponent;
}
复制代码
2、响应式原理

v4版本使用Object.defineProperty代理实现响应式,v5版本使用Proxy代理实现响应式

function observer(funCompOrRender){
  const data = {value: 1};
  const listenersMaps = new Map();
  const proxy = new Proxy(data, {
    //收集依赖
    get(target, key){
      if(!listenersMaps.get(target)){
        listenersMaps.set(target, funCompOrRender);
      }
      return target[key];
    },
    //回调更新
    set(target, key, value){
      const result = Reflect.set(target, key, value);
      if(listenersMaps.has(target)){
        listenersMaps.get(target)(target);
      }
      return result;
    }
  })
  return funCompOrRender(proxy)
}

observer(function Component(props){
  document.body.innerHTML = props.value;
})
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享