开发过程中应用Mobx极大的不便了咱们,然而在应用过程中还是会或多或少地遇到一些问题导致绑定失败,上面咱们来一起探讨下Mobx的绑定过程,以不便咱们来更好的应用它。

MobX 会对在追踪函数执行过程中读取现存的可察看属性做出反馈。

MobX的官网文档将MobX的绑定及相应过程总结为这么一句话,并标出了“读取”、“追踪函数”和“过程”三个关键字。

Mobx会收集哪些地方的绑定

“追踪函数” 是 computed 表达式、observer 组件的 render() 办法和 whenreactionautorun 的第一个入参函数。

文档阐明的也比较清楚,会对文件当中的@computed润饰的办法、render()办法、when办法的第一个入参函数、reaction办法的第一个入参函数、autorun办法的第一个入参函数这些中央收集,MobX会在页面加载执行前扫描所有的文件,收集这些中央的绑定。

以下调用和赋值this.store.listA的中央,MobX都会去收集绑定,其它的中央则不会去收集。

//数据绑定文件import { computed, observable } from 'mobx';class IndexStore {    @observable listA = ["1","2","3"];    @observable listB = ["a","b","c"];    @computed get dataA() {        return this.listA;    }}//Index视图文件import { observer } from 'mobx-react/native';import IndexStore from 'indexStore';@observerclass Index extends Component {    constructor() {        super();        this.store = new IndexStore();        when(            () => this.store.listA.length == 0,            () => console.log("listA none")        );    }    const autorun1 = autorun(() => {        console.log(this.store.listA);    })    const reaction2 = reaction(        () => this.store.listA,        listA => console.log(listA.join(", "))    )    render() {        return (            <MainItem dataSource={this.store.listA}            renderHeader={                () => <Item data={this.store.listB}></Item>            } />        )    }} 

MobX会收集哪些绑定

“过程(during)” 意味着只追踪那些在函数执行时被读取的 observable 。这些值是否由追踪函数间接或间接应用并不重要。

这句话解释了MobX收集哪些绑定,是那些在函数执行时被读取的observable,例如下面实例代码<MainItem dataSource={this.store.listA}/>中的this.store.listA,它在render()函数执行时被调用,用于作为MainItem的数据源,所以这个绑定就被收集到了,render()函数和listA之间就建设了分割,当listA发生变化时,render()函数就会被调用,界面也就从新渲染刷新了。同理,下面的一些log也会调用,会在控制台输入相应信息。

须要留神的是下面实例代码中的renderHeader={() => <Item data={this.store.listB}></Item>}当中的this.store.listB并不会被收集到,为什么呢?因为renderHeader作为一个属性传入MainItemrenderHeader当中的this.store.listB并没有在Indexrender()去应用,而是在Item当中应用了它,所以Indexrender()并没有与listB之间建设连贯,当listB发生变化就不会调用Indexrender(),界面也就不会从新渲染刷新。

咱们想在批改listB时让界面刷新作出相应,咱们该怎么办呢?咱们须要在Item当中增加@observer去捕捉这个绑定,因为listBItemrender()当中进行了调用,所以正确的写法如下:

//Index视图文件render() {    return (        <MainItem dataSource={this.store.listA}            renderHeader={                () => <Item data={this.store}></Item>        } />    )}//Item视图文件import { observer } from 'mobx-react/native';@observerclass Item extends Component {    constructor() {        super();    }    render() {        return (            <Text>{this.props.data.listB[0]}</Text>        )    }} 

Tips:当咱们批改listB时,Item就会从新调用其render()函数从新渲染,咱们从新渲染Item比从新渲染整个Index所耗费的资源会更少,尽管MobX曾经有机制帮咱们缩小不必要的渲染,然而这样还是会耗费更少的资源,所以官网举荐咱们绑定粒度越细越好。

仔细的同学可能会留神到为什么Itemdata当中是this.store而不是咱们认为的this.store.listB,是写错了吗,并不是,这恰好是正确的写法,上面咱们就来探讨下这个问题。

MobX绑定了什么

“读取” 是对象属性的间接援用,能够用过 . (例如 user.name) 或者 [] (例如 user['name']) 的模式实现。

这句话解释了MobX绑定了什么,它绑定的是可察看对象的援用,例如下面提到的listA,MobX并不是将listA的内容["1","2","3"]render()函数绑定,而是将listA变量对["1","2","3"]的援用与render()函数绑定绑定,所以只有当listA对值的援用发生变化时,render()函数才会调用。(也能够了解为c/c++当中的指针的概念,render()函数是与listA变量的指针值绑定)

所以针对下面的listA,你只批改数组的内容,render()函数是不会调用的,界面也是不会刷新的。上面这种写法,界面并不会刷新:

//数据绑定文件modifyListA1() {    this.listA[0] = "4";}//此处只是数组["1","2","3"]的内容产生了变动,listA的援用却没有发生变化 

正确的写法

//数据绑定文件modifyListA2() {    this.listA = ["4","2","3"];}//此处listA被赋值了一个新数组["4","2","3"],listA的援用产生了变动 

咱们再回到listB的问题上,如果咱们是<Item data={this.store.listB}></Item>这种写法,咱们相当于把["a","b","c"]这个值传递给了Item当中的data,通过data对数组的操作,并未呈现listB的援用,也就无奈建设绑定。而咱们将整个store传递过来,data就被赋值了store的内容,当data援用listB时,也就呈现了listB的援用,这样绑定才建设了起来。

所以针对子组件,须要将被察看属性的父级传递过来,这样能力在子组件中呈现被察看属性的援用,能力建设绑定。

MobX绑定过程

理解了以上三个概念,MobX的绑定过程就比拟清晰了。MobX在代码编译时/代码执行之前扫描代码中的computed 表达式、observer 组件的 render() 办法等中央,将这些办法中呈现的间接调用的察看属性的援用和这些办法绑定起来,这就是MobX的绑定过程。绑定实现之后,当这些援用发生变化时,相应的绑定办法就调用,界面就会刷新从新渲染或者相应的逻辑就会执行。

observable和@observable

下面咱们modifyListA1提到的批改并不会触发绑定的界面刷新操作,须要咱们应用modifyListA2当中的批改形式,给listA提供一个新数组,扭转它的援用值。那有没有办法能够让listA只批改一个值(如modifyListA1当中的操作)就触发界面的刷新呢,答案是必定的,咱们只须要将listA初始的赋值形式改成如下形式即可:

//数据绑定文件import { computed, observable } from 'mobx';class IndexStore {    listA = observable(["1","2","3"]);    @observable listB = ["a","b","c"];    @computed get dataA() {        return this.listA;    }} 

为什么这种形式就能够呢?因为observable默认状况下会递归利用,相比拟@observable细粒度的察看,只监测listA的援用,observable则会递归这些察看,将listA[0]listA[1]listA[2]的援用都作为监测对象,这样listA[0]listA[1]listA[2]的援用就和render()函数建设起了绑定,当listA[0]被赋值,援用发生变化时,render()也就被调用了,界面也就刷新了。

如果须要一个数据源的外部数据发生变化引起相应操作,咱们能够应用observable,然而observable也有它的弊病,它会建设起来比拟多的冗余绑定,也会使后续的保护变得复杂,总体上咱们举荐应用@observable这样细粒度的管制,它会使咱们的我的项目更加清晰便于保护,同时也会大大的升高那些莫名其妙的bug的概率。