美文网首页
Mobx应用梳理

Mobx应用梳理

作者: LobsterKun | 来源:发表于2018-11-12 11:44 被阅读0次

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

    装饰器语法

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

    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包裹。

    相关文章

      网友评论

          本文标题:Mobx应用梳理

          本文链接:https://www.haomeiwen.com/subject/xzlwxqtx.html