Mobx应用梳理

写在开始,一下均为开发过程中个人的理解,如有错误恳请斧正,后期开发过程有问题或新发现,恳请诸位及时分享。

装饰器语法

语法糖,用于给对象在运行期间动态的增加某个功能,职责等。核心上类似于高阶函数。

function log(target) { // 这个 target 在这里就是 MyClass 这个类
   target.prototype.logger = () => `${target.name} 被调用`
}
// 装饰器只对后面紧跟的对象产生影响
@log
class MyClass { }
const test = new MyClass()
test.logger() // MyClass 被调用

Mobx基本概念

  • State(状态)
    等同于react中的state
  • Derivations(衍生)
    分为Computed 和 Reactions;
    Computed:
    计算属性,对state中某个属性进行加工计算,返回加工计算后的值。
    Reactions:
    反应,当state中某个属性发生改变时需要发生的的副作用。
  • Actions(动作)
    动作,修改state

Mobx核心方法

  • autoRun
    这个函数非常智能,用到了什么属性,就会和这个属性挂上钩,从此一旦这个属性发生了改变,就会触发回调,通知你可以拿到新值了。没有用到的属性,无论你怎么修改,它都不会触发回调。
    在react中的应用:mobx使用autoRun包裹了我们的组件,使这个组件变成受控组件,并且将追踪render生命周期方法内的所有state。

Mobx基础用法

  • 定义一个可追踪state变量
import {observable} from mobx
class test {
  @observable  testStr = '123'
}
  • 定义一个action修改state变量
import {observable,action} from mobx
class test {
  @observable  testStr = '123'
  @action setTestStr(param){
      this.testStr = param
  }
}

action和setState的差别:action是同步的,所以如果你有两个连续的action修改变量,则会刷新两次组件。可以选择合并action。如果一个action中修改多个变量,且存在某个变量是异步修改,则可以考虑使用Transaction替代action。Transaction是mobx底层API,有兴趣的同学自行研究。

  • 获取一个计算值
import {observable,action} from mobx
class test {
  @observable  testStr1 = 123
  @observable  testStr2 = 456
  @computed get testStr(){
      retutn testStr1 + testStr2 
   }
   // 每当testStr1和testStr2 发生改变时,testStr也将发生改变。
}

Mobx对什么做出响应

文档中写到:MobX 追踪属性访问,而不是值。
说的通俗点就是Mobx追踪的是变量在堆栈中的地址变化,而不是地址保存的值。

  • 基础数据类型
import {observable,action} from mobx
class test {
  @observable  testNum = 123
  @observable  testStr = 'dt'
  @observable  testbool = true
  @action setNum (param){
     // this.testNum = 123 // 不响应
     // this.testNum = this.testNum // 不会响应
      this.testNum = param // param变则响应
   }
  @action setStr (param){
     // this.testStr = '123' // 第一次响应
     // this.testStr = this.testStr // 不响应
      this.testStr = param // param变则响应
   }
  @action setBool (param){
     // this.testbool = false // 第一次响应
     // this.testbool = this.testNum  // 不响应
      this.testbool = param // param变则响应
   }
}
  • 对象类型
import {observable,action} from mobx
class test {
  @observable  testObj = {
      parent:{
        child: 'hi,I`m children'
      }
  }
  @observable  testArr = [123,456]
  @action setArr (param){
     // this.testObj = {
     //     parent:{
     //       child: 'hi,I`m children'
     //     }
     //  }
     // 此时不论引用的是testObj还是其中的属性都将做出响应。
     // this.testObj = this.testObj // 不响应。
     // this.testObj = param // 每次一都响应,无论是否变化。
     // this.testObj.parent = {child: 'hi,I`m children'} 
     // 此时如果引用testObj则不响应,引用testObj.parent和testObj.parent.child都将响应。
     // this.testObj.other = '123'
     // 引用testObj,estObj.parent和testObj.parent.child都将不响应。
   }
   @action setChild(param){
     // this.testObj.parent.child = param //引用this.testObj.parent.child, param变则响应
     // this.testObj.parent = {child: 'hi,I`m children'}//引用this.testObj.parent.child, 次次响应
      this.test.other = {child:'hi,I`m other'} //引用this.testObj.parent.child, 不响应
   }
  @action setArr(param){
     // this.testArr.push(789) // 引用this.testArr,不响应,引用this.testArr.length次次响应。引用this.testArr[n],n小于length响应,n大于length不响应
     // this.testArr =  [123,456] // 次次响应
      this.testArr  = [].concat(this.testArr) // 次次响应
   }
}

数组:数组在mobx中有别于其他类型,对数组进行push,如果直接在autorun中引用这个数组,并不会响应,但是如果引用数组的方法或者数组范围内的某个值都将触发响应。<div>{this.props.store.arr}<div>这种写法也会触发响应,其原理类似应用了数组的map方法。

Mobx处理异步或者并发

  • promise
    错误做法
class Store {
    @observable githubProjects = []
    @observable state = "pending" // "pending" / "done" / "error"

    @action
    fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        fetchGithubProjectsSomehow().then(
            projects => {
                const filteredProjects = somePreprocessing(projects)
                this.githubProjects = filteredProjects
                this.state = "done"
            },
            error => {
                this.state = "error"
            }
        )
    }
}

以上操作的将会抛出异常,原因为fetchGithubProjectsSomehow的异步回调函数不属于fetchProjects动作的一部分,简单的说就是this指向不对。最简单的修改方式就是再定义两个action,分别作为fetchGithubProjectsSomehow的两个异步回调。但是此方法需要额外定义,所以放弃采用action内联写法。

class Store {
    @observable githubProjects = []
    @observable state = "pending" // "pending" / "done" / "error"

    @action
    fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        fetchGithubProjectsSomehow().then(
            // 内联创建的动作
            action("fetchSuccess", projects => {
                const filteredProjects = somePreprocessing(projects)
                this.githubProjects = filteredProjects
                this.state = "done"
            }),
            // 内联创建的动作
            action("fetchError", error => {
                this.state = "error"
            })
        )
    }
}
  • async / await写法
    async / await其实就是promise的封装,已经被es支持,好处是可以使代码看起像是同步代码,易于理解。
    需要注意的点是await必须写在async函数内部,不可单独存在。
class Store {
    @observable githubProjects = []
    @observable state = "pending" // "pending" / "done" / "error"

    @action
    async fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        try {
            const projects = await fetchGithubProjectsSomehow()
            const filteredProjects = somePreprocessing(projects)
            // await 之后,再次修改状态需要动作:
            runInAction(() => {
                this.state = "done"
                this.githubProjects = filteredProjects
            })
        } catch (error) {
            runInAction(() => {
                this.state = "error"
            })
        }
    }
}
  • flows
    此方案基本等同于async/await方法。其属于mobx内置概念,可取消这次异步,有兴趣的同学自行研究。
  • 并发请求
    方案1:使用promise.all()方法。
    方案2:使用async/await,逐一执行异步
  • 在异步执行完毕做一系列处理
    方案1:定义好一个函数,作为回调传给action,异步完毕直接调用。
    方案2:在action中返回一个promise
    方案3:async/await 同步化

When,Reaction用法

两种方法均为state改变后的副作用

  • when
    when方法有两个参数。均为函数。方法会自动运行第一函数,知道第一个函数返回true,则运行第二个函数。所以第一个函数相当于一个检测函数,当state中的某些值变化为你需要的值时返回true,然后执行对应的响应函数。
    经测试,第一个参数需要传递一个computed,来自动响应state变化。同时when需要写在constructor内,否则会被当成原生的when处理。
class MyResource {
    constructor() {
        when(
            // 一旦...
            () => !this.isVisible,
            // ... 然后
            () => this.dispose()
        );
    }

    @computed get isVisible() {
        // 标识此项是否可见
    }

    dispose() {
        // 清理
    }
}
  • reaction
    reaction接收接收两个参数,均为函数,第一个函数需要用到你要监测的state并且返回一个值,此返回值将作为第二个函数的参数,第二个函数接收两个参数,第一个为前一个函数的返回值,第二个参数为reaction,调用reaction.dispose方法可以清楚这个reaction
const todos = observable([
    {
        title: "Make coffee",
        done: true,
    },
    {
        title: "Find biscuit",
        done: false
    }
]);

// reaction 的错误用法: 对 length 的变化作出反应, 而不是 title 的变化!
const reaction1 = reaction(
    () => todos.length,
    length => console.log("reaction 1:", todos.map(todo => todo.title).join(", "))
);

// reaction 的正确用法: 对 length 和 title 的变化作出反应
const reaction2 = reaction(
    () => todos.map(todo => todo.title),
    titles => console.log("reaction 2:", titles.join(", "))
);

// autorun 对它函数中使用的任何东西作出反应
const autorun1 = autorun(
    () => console.log("autorun 1:", todos.map(todo => todo.title).join(", "))
);

todos.push({ title: "explain reactions", done: false });
// 输出:
// reaction 1: Make coffee, find biscuit, explain reactions
// reaction 2: Make coffee, find biscuit, explain reactions
// autorun 1: Make coffee, find biscuit, explain reactions

todos[0].title = "Make tea"
// 输出:
// reaction 2: Make tea, find biscuit, explain reactions
// autorun 1: Make tea, find biscuit, explain reactions
const counter = observable({ count: 0 });

// 只调用一次并清理掉 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)

console.log(counter.count);
// 输出:
// 2

Mobx在项目中的应用方法

  • 定义store
    在src目录下的mudles中新建自己的文件夹,书写自己的store
  • 实例化store
    在src/store目录下的index中导入自己的store文件,并new一个自己store的实例,new出来的实例的名字将作为往后在props中你自己store的对象key值。
  • 将自己的store注入到自己的模块
import React, { Component } from "react";
import { observer,inject } from "mobx-react";
@inject('你自己store的名称,如果是多个store,用逗号隔开')
// 注意@observer后必须紧跟class,否则将不起效果
@observer
class Test extends React.Component {
  render() {
    return (
      <div>
        <childComponent store={this.props.yourStoreName}/>
      </div>
    );
  }
}

export default Test;
import React, { Component } from "react";
import { observer } from "mobx-react";

@observer
class child extends React.Component {
  render() {
    return (
      <div>
       {this.props.store.arr}
      </div>
    );
  }
}

export default child;

项目中只是用请参见命令行模块代码。注:命令行模块在render方法中先定义变量接收store中的值,再进行引用是为了方便代码阅读,更容易知道此组件引用了哪些store中的变量。

最后注意事项

Mobx中的数组,对象均不同于原生的数组和对象,均使用了proxy,但是并不影响使用,可以调任何原生的方法。如果需要转成的原生的,可以调用toJS方法,此方法接收一个proxy值,返回一个原生值。
后续mobx版本可能从5降为4(为了支持低版本浏览器),将为4之后mobx数组使用isArray将不能判断是否为数组,因此将mobx数组传入某些第三方插件时会出错,所以如果此值要传入第三方插件,希望大家都使用toJS包裹。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容