react+redux与react+mobx

react+redux与react+mobx_第1张图片
前要:了解react的状态管理前先回忆一下VUE中的状态管理VUEX,其实也是后者借鉴的前者,其实对于我来说,管你情感还是生活,经历后才知道其中味!!!

Redux与Mobx 适用场景:多交互、多数据源

1、用户使用方式较复杂
2、不同身份用户有不同使用方式(比如普通用户和管理员权限控制)
3、多个用户之间可协调合作
4、与服务器大量交互,或者使用了 WebSocket
5、View视图要从多个来源获取数据

从组件角度看,如果你应用有以下场景,可以考虑用 Redux或Mobx。

1、某组件状态需共享
2、某状态需在任何地方都可以拿到
3、一个组件需要改变全局状态
4、一个组件需要改变另一个组件状态

以上情况时,如不用 Redux 或者其他状态管理工具,不按照一定规律处理状态读写,代码很快就会变成一团乱麻。你需要一种机制,可以在同一个地方查询状态、改变状态、传播状态的变化。

Redux vs Mobx

那么具体到这两种模型,又有一些特定的优缺点呈现出来,先谈谈 Redux 的优势:

数据流流动很自然,因为任何 dispatch 都会导致广播,需要依据对象引用是否变化来控制更新粒度。
如果充分利用时间回溯的特征,可以增强业务的可预测性与错误定位能力。
时间回溯代价很高,因为每次都要更新引用,除非增加代码复杂度,或使用 immutable。
时间回溯的另一个代价是 action 与 reducer 完全脱节,数据流过程需要自行脑补。原因是可回溯必然不能保证引用关系。
引入中间件,其实主要为了解决异步带来的副作用,业务逻辑或多或少参杂着 magic。
但是灵活利用中间件,可以通过约定完成许多复杂的工作。
对 typescript 支持困难。

Mobx:
数据流流动不自然,只有用到的数据才会引发绑定,局部精确更新,但免去了粒度控制烦恼。
没有时间回溯能力,因为数据只有一份引用。
自始至终一份引用,不需要 immutable,也没有复制对象的额外开销。
没有这样的烦恼,数据流动由函数调用一气呵成,便于调试。
业务开发不是脑力活,而是体力活,少一些 magic,多一些效率。
由于没有 magic,所以没有中间件机制,没法通过 magic 加快工作效率(这里 magic 是指 action 分发到 reducer 的过程)。
完美支持 typescript。

到底如何选择

从目前经验来看,我建议前端数据流不太复杂的情况,使用 Mobx,因为更加清晰,也便于维护;如果前端数据流极度复杂,建议谨慎使用 Redux,通过中间件减缓巨大业务复杂度,但还是要做到对开发人员尽量透明,如果可以建议使用 typescript 辅助。

简单实现react+redux

一张图理解redux:
react+redux与react+mobx_第2张图片
react+redux与react+mobx_第3张图片

图解:
Components相当甲方,甲方不断提出需求(Action Creators),需求中包括着详细配置(action对象),老板(Store)收到这些需求,这些需求分为了要做任务的类型和数据,之后老板将这需求分配给程序员(Reducers),如果这是甲方第一次提需求(初始化),那么previousState就是undefined,type的值为@@init;如果是第二次或以上,previousState就是上一次需求的内容。程序员在完工后将结果作为newState反馈给老板,甲方通过getState方法到老板那拿到成品。

强调点:
1.Reducers初始化的时候是Store自动触发的。
2.在组件中dispatch,调用对象是引入进来的store。
3.react自身setState后可以修改数据,并且自动调用render;但是redux只会更改一次状态,因此需要手动调用render,即在检测到store有一次变化后就调用。

store.subscribe(()=>{
    ReactDOM.render(<App/>, document.getElementById('root'))
})

4.其实action有两种值,当action为对象{type: data:}时表示同步action;为function时表示异步action,为方便理解异步action,还是用前面的例子:甲方和老板说等一年后再把结果发出来,于是由老板的程序员掐表等一年以后再发布。因此,组件等待的这个动作需通过Action Creators。

// component里的index.jsx
 
testAsync = ()=>{
    const { value } = this.selectNumber
    store.dispatch(creatAsyncAction(value*1,500))    // 数字String转Number -1即可
}
 
// action.js
 
export const creatAsyncAction = (data, time)=>{
    return ()=>{
        setTimeout(()=>{
            // 异步任务交给同步完成
            store.dispatch({type: 'INCREMENT', data})
        },time)
    }
}

但是这样写又会出现问题‘Actions must be plain objects’,因为store只能识别object类型的参数,否则无法交给reducer干活,action为函数指的是异步函数里存在同步任务。所以添加一个中间件告诉store,action的函数里存在着object类型的action,让store解析这个函数

npm i redux-thunk / yarn add redux-thunk
 
// store.js
// 引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
import { createStore, applyMiddleware} from 'redux'
// 引入testReducer,为test组件服务的reducer
import testReducer from './test_reducer'
 
export default createStore(testReducer,applyMiddleware(thunk))

安装:

npm install --save redux

配置:创建store目录
index.js:

/*
	该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore} from 'redux'
//引入为Count组件服务的reducer
import reducer from './reducer';
//暴露store
export default createStore(reducer)

reducer.js:

const defaultState = 0

//reducer可以接收state,但是不能修改state
export default (state = defaultState,action) => {
	const { type, data } = action;
    switch (type) {
    case "increment":
      return data + state;
    default:
      return state;
  }
}

在组件中就可以使用store的数据:

import React, { Component } from "react";
import store from "../store/index";
import "./index.css";

export default class index extends Component {
  getIncrement = () => {
    let val = this.getInpVal.value
    store.dispatch({
      type: "increment",
      data: val*1,
    });
  };
  getDecrement = () => {
    let val = this.getInpVal.value
    store.dispatch({
      type: "decrement",
      data: val*1,
    });
  };
  getIncrementOdd = () => {
    if (store.getState()%2!=0) {
      let val = this.getInpVal.value
      store.dispatch({
        type: "increment",
        data: val*1,
      });
    }
  };
  getIncrementOAsync = () => {
    setTimeout(() => {
      let val = this.getInpVal.value
      store.dispatch({
        type: "increment",
        data: val*1,
      });
    }, 1000);
  };

  render() {
    return (
      <div>
        <h3>累加和为:{store.getState()}</h3>
        <select ref={c => this.getInpVal = c}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>
        <button onClick={this.getIncrement}>+</button>
        <button onClick={this.getDecrement}>-</button>
        <button onClick={this.getIncrementOdd}>奇数加</button>
        <button onClick={this.getIncrementOAsync}>异步加</button>
      </div>
    );
  }
}

简单实现react+mobx

Mobx是通过函数响应式编程使状态管理变得简单和可扩展的状态管理库。Mobx和Redux一样,采用单向数据流管理状态:通过action改变应用的state,state的改变触发相应ui的更新,如下图所示:

一张图理解mobx:
react+redux与react+mobx_第4张图片
react+redux与react+mobx_第5张图片

图解:
Mobx是通过函数响应式编程使状态管理变得简单和可扩展的状态管理库。Mobx和Redux一样,采用单向数据流管理状态:通过action改变应用的state,state的改变触发相应ui的更新。

State: 状态,应该是应用依赖的最小状态集,没有任何多余的状态,也不需要通过其他状态计算而来的中间状态;
Computed value: 计算值,是根据state推导计算出来的值;
Reaction: 响应,受state影响,会对state的变化做出一些更新ui、打印日志等反应;
Action: 动作,建议是唯一可以修改状态的方式;

Mobx整体是一个观察者模式,存储state的store是被观察者,使用store的组件是观察者。当action改变被观察的state之后,computed value和reactin会自动根据state的改变做最小化更新,需要注意的是computed value采用延迟更新的方式,只有待更新的computed value被使用时才会被重新计算,不然,computed value不仅不会被重新计算,还会被自动回收。

安装:

npm install mobx mobx-react

配置装饰器( 修饰器 es6 ) babel

npm install babel-plugin-transform-decorators-legacy -D
npm install @babel/preset-env -D
npm install babel-plugin-transform-class-properties -D
npm install @babel/plugin-proposal-decorators -D

配置package.json

 "babel": {
        "plugins": [
          [
            "@babel/plugin-proposal-decorators",
            {
              "legacy": true
            }
          ],
      "transform-class-properties"
    ],
    "presets": [
      "react-app",
      "@babel/preset-env"
    ]
    },

注意: 其中两个配置顺序不可更改

   [
        "@babel/plugin-proposal-decorators",
        {
          "legacy": true
        }
      ],
      "transform-class-properties"

项目中 mobx应该怎么用?

在创建store的时候,需要使用class来创建一个状态,再通过实例化对象来使用状态

import {action, observable} from "mobx";

class userStore{
    //@observable观察数据变化
    @observable num=0;
    @computed    //当数据变化时发生改变时, 自动触发
    get(只读) num(){
         return this.num * 2
       }
    //@action修改状态的方法/事件
    @action
    change(){
        this.num++;
    }
}

export default userStore

在store/index.js进行其他store的实例化处理

//导入需要实例化的store
import userStore from "./userStore";

let user=new userStore();
const store={
    user
}
export default store

在入口文件中 使用Provider来传入数据

//导入需要使用的包和store
import {Provider} from "mobx-react";
import store from "./store/index";

//provider组件将我们的store注入App组件
//provider相当于一个数据容器

ReactDOM.render(
    //provider不同的传值方式导致的后面调用方式发生改变
    //第一种:
    <Provider store={store}>
        <App/>
    </Provider>,
    //第二种:解构之后
    <Provider {...store}>
        <App/>
    </Provider>,
    document.getElementById('root')
)
;

需要在那个组件里面使用store里面的值就在当前组件中引入inject[‘provider的名字’]在页面中的获取
这是使用store={store}

this.props.store['这是inject的名称'].user['这是store的状态变量名']
//虽然this.porps里面没有找到 @action 装饰器定义的方法, 但是可以直接使用,
//直接使用的方法就是
this.props.store.user.change()
//可以嵌套在其他页面事件触发时使用
//但是由于this会丢失的原因
//点击事件要加上 bind(this)
this.props.store.user.bind(this)

这是使用{…store}

//可以直接访问变量名
this.props.user['这是store的状态变量名']
//虽然this.porps里面没有找到 @action 装饰器定义的方法, 但是可以直接使用,
//直接使用的方法就是
this.props.user.change()
//可以嵌套在其他页面事件触发时使用
//但是由于this会丢失的原因
//点击事件要加上 bind(this)
this.props.store.user.bind(this)

你可能感兴趣的:(REACT,react.js,前端,reactjs)