背景
React的自身的状态本身由state
和setState
维护;但是随着应用复杂度的提升(组件间状态共享及状态变更),单纯通过setState
进行状态管理的方案不仅变得代码复杂、难以维护,而且影响可扩展性;
针对这种状况,React目前主要有Redux
和mobx
两种解决方案(均可以让状态逻逻辑从展示组件等解耦出来,提高扩展性与复用性);
mobx与Redux有什么不同?
-
Redux
采用FP原则,核心为action
、store
、reducer
,采用单一数据源,状态不可变更(总是返回一个新状态),使用纯函数进行状态变更;Redux
更新时总是会返回一个新的state(redux底层的combineReducers
会通过比较对象的引用来判断是否同一个对象,如果是的话则继续使用旧的state,即不会有任何UI更新,否则执行更新操作)为什么redux要求不可变性 ?
- 浅比较:redux的
combineReducers
及react-redux的connect
方法
生成的组件在比较根组件state与mapStateToProps
函数返回值以确定是否更新时,都用到了浅比较;浅比较的前提是数据不可变; -
不可变数据的管理极大地提升了数据处理的安全性。
-
进行时间旅行调试要求 reducer 是一个没有副作用的纯函数,以此在不同 state 之间正确的移动。
什么是时间旅行?
在 2015 年的 React Europe 会议上,Dan Abramov 展示了通过 Redux DevTools 让开发者在历史状态中自由穿梭, 提升调试体验
时间旅行
主要用于事件驱动的应用,比如富文本编辑器的状态回撤、游戏进度的读取和保存等等;具体参考从时间旅行的乌托邦,看状态管理的设计误区(state, action) => newState
- 浅比较:redux的
-
mobx
采用OOP原则,也是响应式编程;采用observable
和observer
的概念,observable
中将对象变为被观察对象,可以通过action
等变更状态;observer
定义观察对象,可以观察被观察对象的变更并应用;mobx
中状态是可变的,更加轻量级,入门比Redux
简单;需要注意的是,MobX 在重绘时的性能优势是以访问劫持后更大的内存占用为代价的;(开箱即用的mobx与优化后的redux性能相当)
-
要点比较
Mobx概述
mobx支持单向数据流,由原始状态衍生的值都会自动变更,无法观察到中间值;
- 创建应用模型,定义状态使其可观察(建立store,即被观察对象)
- 创建视图以响应状态的变化(创建观察者,检测并响应状态变化)
- 更改状态
环境配置
mobx使用装饰器特性的话需要开启ES7的decorator属性;可以安装插件@babel/plugin-proposal-decorators"
在package.json
中添加以下配置:
"babel": {
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
],
"presets": [
"react-app"
]
}
否则,可以借助mobx API —— decorate(object, decorators)
实现部分功能
Mobx API —— observable相关
-
描述:observable用于创建被观察对象(定义可观察状态),需要注意的是被观察对象总是类的属性而不是值;
-
用法:
// 用法一, 会自动递归到整个对象 observable(value) // 用法二 是extendObservable(this, { property: value})的语法糖 @observable classProperty = value // demo import { observable, computed } from "mobx"; class Order { @observable price = 0; @observable count = 1; @computed get total() { return this.price * this.count; } @action.bound increasePriice() { this.price++; } } // demo2 —— 使用decorate重写demo import { observable, computed, decorate } from 'mobx'; class Order { price = 0; count = 1; get total() { return this.price * this.count; } increasePriice() { this.price++; } } decorate(Order, { price: observable, count: observable, total: computed, increasePrice: action.bound })
Mobx API —— 对observable做出响应
1. computed——计算值
-
含义:computed可以根据现有的状态或其他计算值衍生出新的值,并且这些值可以被observer使用;
autorun
主要适用于产生一个副作用而不是一个新值,如日志打印和网络请求等; -
要点:
computed
是响应式的,如果前一个依赖计算的值未发生变化,计算属性不会重新运行,这种情况下它会被暂停(自动暂停特性)。同时,如果一个计算属性不再被观察,其将会自动进行垃圾回收。不要把 computed 和 autorun 搞混。它们都是响应式调用的表达式,但是,如果你想响应式的产生一个可以被其它 observer 使用的值,请使用 @computed,如果你不想产生一个新值,而想要达到一个效果,请使用 autorun。 举例来说,效果是像打印日志、发起网络请求等这样命令式的副作用。
-
用法:
// 用法一:函数形式 computed(expression) // demo import {observable, computed} from "mobx"; var name = observable.box("John"); var upperCaseName = computed(() => name.get().toUpperCase() ); var disposer = upperCaseName.observe(change => console.log(change.newValue)); name.set("Dave"); // 输出: 'DAVE' // 用法二:装饰器。在任意属性的getter上使用 @computed get propertyName() { return }
2. autorun——自定义反应
-
含义:如果创建一个函数,本身不会有观察者,并且其不产生新值而是用于产生一个副作用(网络请求/日志打印/更新UI)等时,需要使用
autorun
; -
要点:
computed(function) 创建的函数只有当它有自己的观察者时才会重新计算,否则它的值会被认为是不相关的。而autorun
创建后会立即运行一次; -
用法
第一个参数一般为函数(如果是字符串,将被用作调试名),第二个参数为可选的对象参数,包括属性:delay
(去抖时间)/name
(reaction名称)/onError
(处理reaction错误)/scheduler
(设置自定义调度器决定autorun
函数如何重新运行)autorun(() => { xxx }, { delay: 200 })
3. reaction——自定义反应
-
含义:可以将
reaction
看作是autorun
的变种,为`observable的追踪提供了更加细粒度的控制; -
用法:
第一个为数据追踪函数,第二个为副作用函数(其中的任何observable都不会被追踪);第三个为可选的对象参数;
只用在数据追踪函数返回新值时副作用函数才会运行;reaction(() => data, (data, reaction) => { sideEffect }, options?)
-
demo:
// 只调用一次并清理掉 reaction : 对 observable 值作出反应。 const reaction3 = reaction( () => counter.count, (count, reaction) => { console.log("reaction 3: invoked. counter.count = " + count); reaction.dispose(); } ); counter.count = 1; // 输出: // reaction 3: invoked. counter.count = 1 counter.count = 2; // 输出: // (There are no logging, because of reaction disposed. But, counter continue reaction)
4. @observer——函数装饰器
-
含义:可以将React组件转为响应式组件,响应被观察对象的改变;
-
要点:它用 mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件
-
用法:
当 observer 需要组合其它装饰器或高阶组件时,请确保 observer 是最深处(第一个应用)的装饰器,否则它可能什么都不做。
observer(class Demo ... { }) @observer class Demo extends React.Component { ... } const Demo = observer(({ data }) => <span>type: { data.type } </span> );
5. 使用 inject 将组件连接到提供的 stores
import { observer, inject} from 'mobx-react';
import React from 'react';
@inject('store')
@observer
class Casual extends React.Component {
render() {
return (
<div>
<h2>num: {this.props.store.num}</h2>
<button onClick={this.handleClick}>increment</button>
</div>
)
}
handleClick = () => {
this.props.store.increment();
}
}
export default Casual;
// store.js
import { action, observable, computed, decorate } from 'mobx';
class Store {
@observable num = 0;
@computed get retNum() {
return `the value of num is ${this.num}`
}
@action.bound
increment() {
this.num++;
}
}
export default Store;
6. componentWillReact
-
含义:当组件因为它观察的数据发生重新渲染时,会触发该生命周期函数;可以追溯渲染并找到导致渲染的操作
-
要点:
componentWillReact
不接受任何参数;初始化渲染时不会被触发;@observer 以和 PureComponent 同样的方式实现了 shouldComponentUpdate,因此子组件可以避免不必要的重新渲染
-
demo:
import {observer} from "mobx-react"; @observer class TodoView extends React.Component { componentWillReact() { console.log("I will re-render, since the todo has changed!"); } render() { return <div>this.props.todo.title</div>; } }
Mobx API —— 改变observables
1. action
-
含义:用于修改状态;
-
用法
action(fn) action(name, fn) @action classMethod() {} @action(name) classMethod() {} // @action.bound可以自动绑定到目标对象(即指定了this作用域) @action.bound classMethod() {} ...
-
action.bound不要和箭头函数一起使用;