最近因为实际工作中有用mobx,所以特此学习总结一下
概念
- 理念是通过观察者模式对数据做出追踪处理,在对可观察属性的作出变更或者引用的时候,触发其依赖的监听函数。这一点和Vue通过
Object.defineProperty,在对状态进行读写操作的时候会触发其 getter 和 setter 函数以进行响应的原理其实是非常类似的。在5版本中升级了使用proxy来对数据进行响应式 - mobx数据流是单向的,在整个数据流中,通过事件驱动(UI 事件、网络请求…)触发 Actions,在 Actions 中修改了 State 中的值,这里的 State 即应用中的 store 树(存储数据),然后根据新的 State 中的数据计算出所需要的计算属性(computed values)值,最后响应(react)到 UI 视图层。
环境准备
安装依赖模块
pm i webpack webpack-cli babel-core babel-loader
babel-preset-env babel-preset-react babel-preset-stage-0
babel-plugin-transform-decorators-legacy mobx mobx-react -D
webpack.config.js
const path=require('path');
module.exports = {
mode: 'development',
entry: path.resolve(__dirname,'src/index.js'),
output: {
path: path.resolve(__dirname,'dist'),
filename:'main.js'
},
module: {
rules: [
{
test: /.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['env','react','stage-0'],
plugins:['transform-decorators-legacy']
}
}
}
]
},
devtool:'inline-source-map'
}
.babelrc
- 配置在react中,通过
.babelrc即可配置es7的装饰器语法:
{ "presets":
[ "es2015", "stage-1", "react" ],
"plugins": ["transform-decorators-legacy"]
}
当然,你需要
$ npm i babel-preset-{es2015,stage-1,react} babel-plugin-transform-decorators-legacy
对于NodeV5以上版本,无需--save就会自动帮你安装到dependencies中对于decorators,如果你使用create-react-app创建项目, 需要:
$ npm i babel-preset-stage-2 babel-preset-react-native-stage-0 --save-dev
然后在babelrc文件中输入:
{ "presets": ["react-native-stage-0/decorator-support"] }
4.3 package.json
"scripts": {
"start": "webpack -w"
}
@observable
observable 的属性值在其变化的时候 ,mobx 会自动追踪并作出响应。其语法为
// store.js
import { observable } from 'mobx';
class Store {
@observable a = 0;
@observable bbb = 'str';
@observable c = {};
//
@action changeA(val) { this.a = val; }
}
export default new Store();
@observable 接受任何类型的 js 值(原始类型、引用、纯对象、类实例、数组和、maps) 整体步骤如下:
- 处理装饰器,准备好装饰器所需的参数
装饰器书写有带括号和不带括号的,但最后都要求返回 descriptor
@observable obj ...
@log({ name: 'lawler' }) obj2 ...
- 劫持对象当前层的属性
根据属性值的类型,调用的相应 enhancer(即劫持器,后面会说到)进行劫持
- 递归劫持
判断是否为引用类型,如果是,则递归劫持
- 暴露操作 api,方便用户操作,如 mobx 的 keys, values, set
源码分析可参考: juejin.cn/post/684490…
值得注意的一点是,当某一数据被 observable 包装后,他返回的其实是被 observable 包装后的类型。
import { observable, autorun, computed, action } from 'mobx'
class Store {
@observable list: Number[] = []
}
const store = new Store()
console.log('list is Array?',Array.isArray(list))
console.log('list:'store.list)
复制代码
控制台输出:
list is Array? false
list Proxy {Symbol(mobx administration): ObservableArrayAdministration}
.
@observer
@observer 是mobx-react提供的,通过使用@observer,将react组件转换成一个监听者,这样在被监听的应用状态变量(Observable)有更新时,react组件就会重新渲染。当 render 中的 state发生改变时, mobx-react 会重新调用 render 方法,重新渲染这个组件.
// myComponent.jsx
import React from 'react';
import { observer } from 'mobx-react';
import store from './store.js';
@observer
class MyComponent extends React.Component{
render() {
return (
<div> <p>{store.a}</p>
<button onClick={store.changeA(store.a + 1)}>给a增加1</button> </div> )
}
}
export default MyComponent;
observer通过es7的装饰器模式,将一个React组件作为参数,并将其转为响应式(Reactive)组件。
Computed
其语法为:
@computed get computesValue [function]
我们视图的值改变依赖模型值也就是我们observable里面数据的时候可以使用,原则上在computed不对状态进行数据修改
class Store {
@observable list = []
@computed get total() {
return this.list.length
}
}
当被@observable修饰的list变动时,total才会重新计算。
action
@action是mobx提供的,其规定对于 store 对象中所有可观察状态属性的改变都应该在 @action 中完成,凡是涉及到对应用状态变量修改的函数,都应该使用@action修饰符。从上图也可以看出,action会触发状态的改变
语法形式:
@action actionFuncName[function]
class Store {
@observable list = []
@computed get total() {
return this.list.length
}
@action change() { this.list.push(this.list.length) } }
}
AutoRun
autorun 接受一个函数作为参数,在使用 autorun 的时候,该函数会被立即调用一次,之后当该函数中依赖的可观察状态属性(或者计算属性)发生变化的时候,该函数会被调用,注意,该函数的调用取决的函数中使用了哪些可观察状态属性(或者计算属性)。
import { observable, action, computed, autorun } from `mobx`;
class Store {
@observable count = 0;
@action add () { this.count = this.count + 1; }
};
const mstore = new Store();
setInterval(() => { mstore.add(); }, 2000);
autorun(() => { console.log(mstore.count); });
class Store {
@observable count = 0;
@action add () {
this.count = this.count + 1;
}
}
const mstore = new Store();
var sId = setInterval(() => {
mstore.add();
}, 2000);
autorun(() => {
if(mstore.count >2){
clearInterval(sId)
}
console.log(mstore.count);
});
when
- when 观察并运行给定的 predicate,直到返回true。
- 一旦返回 true,给定的 effect 就会被执行,然后 autorunner(自动运行程序) 会被清理。
- 该函数返回一个清理器以提前取消自动运行程序。
when(predicate: () => boolean, effect?: () => void, options?)
let dispose = when(() => store.age>=18,()=>{
console.log('你已经成年了!')
});
dispose();
store.age=10;
store.age=20;
store.age=30;
reaction
- autorun的变种,autorun会自动触发,reaction对于如何追踪observable赋予了更细粒度的控制
- 它接收两个函数参数,第一个(数据 函数)是用来追踪并返回数据作为第二个函数(效果 函数)的输入
- 不同于autorun的是当创建时效果 函数不会直接运行,只有在数据表达式首次返回一个新值后才会运行
- 可以用在登录信息存储和写缓存逻辑
reaction(() => [store.province,store.city],arr => console.log(arr.join(',')));
store.province='山东';
store.city='济南';
全局注册加注入
Provider组件
在react中,mobx-react提供了 Provider 组件用来包裹最外层组件节点,并且传入 store(通过)context 传递给后代组件:
import React from 'react';
import ReactDOM from 'react-dom';
import store from './store';
import { Provider } from 'mobx-react';
import App from './App';
// 写成函数组件的形式
const Root = () => (
<Provider rootStore={store}><App /></Provider>
);
ReactDOM.render(<Root />, document.getElementById('root'))
@Inject
@inject 是为了向当前被装饰的组件 注入 store 这个props。当然 store 这个 prop 其实是由 Provider 提供的
import React from 'react';
import { inject, observer } from 'mobx-react';
@inject('a', 'bbb', 'changeA')
@observer
class myComponent extends React.Component {
render() {
<div> <p>{this.props.a}</p>
<p>{this.props.bbb}</p>
<button onClick={this.props.changeA(store.a + 1)}>给a增加1</button> </div>
}
}
export default MyComponent;
参考资料: juejin.cn/post/687601…