前要:了解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:
图解:
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:
图解:
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)