追踪原理
MobX 会对在执行 跟踪函数 期间 读取的任何现有的可观察属性做出反应
“读取” 是对象属性的间接引用,可以用过 . (例如 user.name) 或者 [] (例如 user[‘name’]) 的形式完成。
“追踪函数” 是 computed 表达式、observer 组件的 render() 方法和 when、reaction 和 autorun 的第一个入参函数。
“过程(during)” 意味着只追踪那些在函数执行时被读取的 observable 。这些值是否由追踪函数直接或间接使用并不重要。
换句话说,MobX 不会对其作出反应:
从 observable 获取的值,但是在追踪函数之外
在异步调用的代码块中读取的 observable
Mobx 5 以下 MobX 不会追踪还不存在的索引或者对象属性(当使用 observable 映射(map)时除外)。
所以建议总是使用 .length 来检查保护基于数组索引的访问。
所有数组的索引分配都可以检测到,但前提条件必须是提供的索引小于数组长度。
核心概念
追踪属性访问,而不是值
1 | let message = observable({ |
mobx会追踪箭头有没有变化
如果箭头发生变化,就会执行追踪函数
使用注意
处理数据时
1.更改没有被obserable的箭头,追踪函数不执行
2.追踪函数里使用间接引用指向obserable属性,追踪函数不执行
3.对新增的属性,可以使用set,get实现obserable
4.在异步代码中访问的obserable属性,不会引起追踪函数执行
1.更改没有被obserable的箭头,追踪函数不执行
autorun(() => {
console.log(message.title)
})
message = observable({ title: "Bar" }) //指向message的箭头没有被obervable
autorun(() => {
message.likes;//箭头没变,又没有访问数组里面的属性
})
message.likes.push("Jennifer");
2.追踪函数里使用间接引用指向obserable属性,追踪函数不执行
var title = message.title;
autorun(() => {
console.log(title) //访问箭头没有变,还是指向老值的位置
})
message.title = "Bar" //箭头改了,但autorun里没有用到
const author = message.author;
autorun(() => {
console.log(author.name)
})
message.author.name = "Sara";//会执行跟踪函数,autorun里有访问name属性,这里指向name值得箭头改了
message.author = { name: "John" };//不会执行,没有访问author属性的箭头
正确使用
A:
autorun(() => {
console.log(message.author.name)
})
message.author.name = "Sara";
message.author = { name: "John" };
B:
function upperCaseAuthorName(author) {
const baseName = author.name;
return baseName.toUpperCase();
}
autorun(() => {
console.log(upperCaseAuthorName(message.author))
})
message.author.name = "Chesterton"
3.异步
const message = observable({ title: "hello" })
autorun(() => {
console.log(message) //会执行两次,因为console.log是异步的,请确保始终传递不变数据 ( immutable data ) 或防御副本给 console.log。
})
message.title = "Hello world"
autorun(() => {
setTimeout(
() => console.log(message.likes.join(", ")), //异步执行,访问原始数据打印一次
10
)
})
message.likes.push("Jennifer");//不会引起autorun执行
4.MobX 5 可以追踪还不存在的属性
autorun(() => {
console.log(message.postDate)
})
message.postDate = new Date()
组件使用时
子组件问题
MobX 只会为数据是直接通过 render 存取的 observer 组件进行数据追踪
所以当需要将数据传递给子组件时,要保证子组件也是一个obserable组件,可以做出反应
解决办法:
1.将子组件使用obserable函数处理
它用 mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件
2.使用mobx-react的Obserable组件包裹子组件
1 | 方法一:将子组件使用obserable函数处理 |
避免在本地字段中缓存 observable
1 | @observer class MyComponent extends React.component { |
优化,使用计算属性,或者在render函数中进行间接引用
@observer class MyComponent extends React.component {
@computed get author() {
return this.props.message.author
}
其他
1.从性能上考虑,越晚进行间接引用越好
2.数组里面的是对象而不是字符串,那么对于发生在某个具体的对象中发生的变化,渲染数组的父组件将不会重新渲染
1 | const Message = observer(({ message }) => |
变化 | 重新渲染组件 |
---|---|
message.title = “Bar” | Message |
message.author.name = “Susan” | Author (.author 在 Message 中进行间接引用, 但没有改变)* |
message.author = { name: “Susan”} | Message, Author |
message.likes[0] = “Michel” | Likes |
一些 对比
autorun vs compute
当使用 autorun 时,所提供的函数总是立即被触发一次,然后每次它的依赖关系改变时会再次被触发
computed(function) 创建的函数只有当它有自己的观察者时才会重新计算,否则它的值会被认为是不相关的
如果一个计算值不再被观察了,例如使用它的UI不复存在了,MobX 可以自动地将其垃圾回收。
而 autorun 中的值必须要手动清理才行
autorun vs reaction
reaction(() => data, (data, reaction) => { sideEffect }, options?)
它接收两个函数参数,第一个(数据函数)是用来追踪并返回数据作为第二个函数(效果函数)的输入。
传入 reaction 的第二个函数(副作用函数)当调用时会接收两个参数。
第一个参数是由 data 函数返回的值。
第二个参数是当前的 reaction,可以用来在执行期间清理 reaction
reaction 返回一个清理函数。
不同于 autorun 的是当创建时 **效果 **函数不会直接运行,只有在数据表达式首次返回一个新值后才会运行。
在执行 效果函数时访问的任何 observable 都不会被追踪。
效果函数仅对数据函数中访问的数据作出反应,这可能会比实际在效果函数使用的数据要少。
此外,效果 函数只会在表达式返回的数据发生更改时触发。 换句话说: reaction需要你生产 效果函数中
所需要的东西。
useObserver vs Observer vs observer
1.虽然只是在返回DOM的地方使用 useObserver(), 但是,当dom中数据改变的时候,整个component都会重新render
1 | function Person() { |
2.Observer 标签组件可以更精准的控制想要重新渲染的地方
1 | export default function ObservePerson() { |
3.与useObserver相比,除了使用方法不同,目前不知道区别在哪,有时间需要探究一下
const ObserverLowercasePerson: React.FC<any> = observer(() => {
console.log('in Observer') //点击按钮也会执行
const person = useLocalStore(() => ({name: 'John'}));
return (
<div>
<div>The name is: {person.name}</div>
<button onClick={() => (person.name = 'Mike')}>
Change name
</button>
</div>
)
})
```