开发过程中应用 Mobx 极大的不便了咱们,然而在应用过程中还是会或多或少地遇到一些问题导致绑定失败,上面咱们来一起探讨下 Mobx 的绑定过程,以不便咱们来更好的应用它。
MobX 会对在追踪函数执行过程中读取现存的可察看属性做出反馈。
MobX 的官网文档将 MobX 的绑定及相应过程总结为这么一句话,并标出了“读取”、“追踪函数”和“过程”三个关键字。
Mobx 会收集哪些地方的绑定
“追踪函数”是
computed
表达式、observer
组件的render()
办法和when
、reaction
和autorun
的第一个入参函数。
文档阐明的也比较清楚,会对文件当中的 @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';
@observer
class 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
作为一个属性传入 MainItem
,renderHeader
当中的 this.store.listB
并没有在 Index
的render()
去应用,而是在 Item
当中应用了它,所以 Index
的render()
并没有与 listB
之间建设连贯,当 listB
发生变化就不会调用 Index
的render()
,界面也就不会从新渲染刷新。
咱们想在批改 listB
时让界面刷新作出相应,咱们该怎么办呢?咱们须要在 Item
当中增加 @observer
去捕捉这个绑定,因为 listB
在Item
的 render()
当中进行了调用,所以正确的写法如下:
//Index 视图文件
render() {
return (<MainItem dataSource={this.store.listA}
renderHeader={() => <Item data={this.store}></Item>
} />
)
}
//Item 视图文件
import {observer} from 'mobx-react/native';
@observer
class Item extends Component {constructor() {super();
}
render() {
return (<Text>{this.props.data.listB[0]}</Text>
)
}
}
Tips:当咱们批改 listB
时,Item
就会从新调用其 render()
函数从新渲染,咱们从新渲染 Item
比从新渲染整个 Index
所耗费的资源会更少,尽管 MobX 曾经有机制帮咱们缩小不必要的渲染,然而这样还是会耗费更少的资源,所以官网举荐咱们绑定粒度越细越好。
仔细的同学可能会留神到为什么 Item
的data
当中是 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 的概率。