mobx学习

196 阅读5分钟

最近因为实际工作中有用mobx,所以特此学习总结一下

概念

  • 理念是通过观察者模式对数据做出追踪处理,在对可观察属性的作出变更或者引用的时候,触发其依赖的监听函数。这一点和Vue通过Object.defineProperty ,在对状态进行读写操作的时候会触发其 getter 和 setter 函数以进行响应的原理其实是非常类似的。在5版本中升级了使用proxy来对数据进行响应式
  • mobx数据流是单向的,在整个数据流中,通过事件驱动(UI 事件、网络请求…)触发 Actions,在 Actions 中修改了 State 中的值,这里的 State 即应用中的 store 树(存储数据),然后根据新的 State 中的数据计算出所需要的计算属性(computed values)值,最后响应(react)到 UI 视图层。

image.png

环境准备

安装依赖模块

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) 整体步骤如下:

  1. 处理装饰器,准备好装饰器所需的参数

装饰器书写有带括号和不带括号的,但最后都要求返回 descriptor

@observable obj ...

@log({ name: 'lawler' }) obj2 ...

  1. 劫持对象当前层的属性

根据属性值的类型,调用的相应 enhancer(即劫持器,后面会说到)进行劫持

  1. 递归劫持

判断是否为引用类型,如果是,则递归劫持

  1. 暴露操作 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…

github.com/hawx1993/te…

juejin.cn/post/687624…

juejin.cn/post/684490… mobx6参考: juejin.cn/post/692154…