Open g770728y opened 5 years ago
runInAction / @action
会批量更新修改const obj = observable({a:1, b:1})
有两种做法:
obj.a=1
action
: runInAction(() => obj.a = 1
runInAction`会导致批量更新, 显然更优
代码如下:const obj = observable({ a: 1, b: 1 });
autorun(() => {
console.log('by autorun', JSON.stringify(obj));
});
// 直接赋值会多次响应
obj.a = 1; <== 第一次打印
obj.a = 2; <== 第二次打印
obj.b = 3; <== 第三次打印
// action赋值只响应一次
runInAction(() => {
obj.a = 1; <== 第一次打印
obj.a = 2; <== 第二次打印
obj.b = 3; <== 第三次打印
}); <= 只打印一次
class Store { x = []
constructor() {
// 观察这里会打印多少次
reaction(()=>this.x.length, len => console.log(len));
}
@action pushX() { this.x.push(1) }
@action pushX2() {this.pushX(); this.pushX()} <== 将只触发单次reaction!!!
}
createTransformer
一句话: 它是computed
的带参数版本 ( computed不可带参 ) , 实现与computed
类似的功能
store, 注意: get 方法
class Store {
@observable
xs: { id: number; a: number }[] = [{ id: 1, a: 1 }, { id: 2, a: 2 }];
get = (id: number) => {
return this.xs.find(it => it.id === id)!;
};
}
const store = new Store();
组件C, 绑定到 store.xs里的id上:
const C: React.FC<{ id: number }> =
observer(({ id }) => {
const x = store.get(id);
console.log('id render', id);
return <div>{x.a}</div>;
})
组件List, 绑定到id列表上:
const List: React.FC = observable(() => {
console.log('app render');
return (
<div>
<button onClick={() => store.xs.push({id:3,a:3})}>push</button>
<button onClick={() => store.xs.pop()}>delete</button>
<button onClick={() => {store.xs[0].a = 5}}>change</button>
<hr />
{store.xs.map(it => (
<C key={it.id} id={it.id} />
))}
</div>
);
})
当你点击上面的push按钮时, 由于id增加了, 所以List肯定刷新,
但 <C id=1>, <C id=2>
会刷新吗? 照理说它们的id并未变化啊.
答案是: 会刷新 ( 原因待分析 )
显然, 对于大量结点的应用, 这是不可接受的.
思考: 如果我们使用了:
@computed
getFirst() {
return this.xs[0]
}
那么 当且仅当 getFirst()的返回值发生变化时, 才会引起刷新.
可是, 上面的get
方法带id
参数, 无法使用computed
幸好, mobx-utils
提供了createTransformer
方法, 用法如下:
class Store {
@observable
xs: { id: number; a: number }[] = [{ id: 1, a: 1 }, { id: 2, a: 2 }];
get: (id: number) => { id: number; a: number };
constructor() {
this.get = createTransformer(this._get);
}
_get = (id: number) => {
return this.xs.find(it => it.id === id)!;
};
}
之后, 其它代码不用修改, 你会发现: <C id=1> <C id=2>
不再无效刷新
原因:
computed
实现了一个内部缓存: 当args
发生变化时, 才会重新计算
createTransformer
也实现了一个内部缓存: 它为每个args
提供一个缓存, 所以基本原理是相通的.
当然, 也正因为如此, 如果某个args
不再可用, 那么缓存显然应该清除
computed
可以保证依赖关系回顾 之前使用到
computed2
依赖computed1
的情形 当时不知为何,computed2
用到的始终是computed1
的旧值 所以一直认为computed
无法正确解析依赖关系
今天仔细想了下, 感觉不太对, 重新验证了下:
class Store {
@observable
obj = { a: 1 };
@computed
get obj_a12() {
const result = this.obj_a1 + this.obj_a2;
console.log('obj_a12');
return result;
}
@computed
get obj_a1() {
const result = this.obj.a + 1;
console.log('obj_a1');
return result;
}
@computed
get obj_a2() {
const result = this.obj_a1 + 1;
console.log('obj_a2');
return result;
}
}
const store = new Store();
autorun(() => {
console.log('this.obj_a1', store.obj_a1);
});
输出的store.obj_a1始终正确
打印出的顺序也始终正确
并且考虑到: obj_a12
在使用this.obj_a1
时, 肯定会去get obj_a1
取值, 必须重算(或取当前缓存)
所以, 至少在简单的a依赖b
这种简单情形, 顺序是不会错的.
补充: 可以简单证明如下:
get A()
, 从而获取到 A1
无论哪种顺序, 由于X 一定是最新值 , 所以 结果一定是对的.一直以为这是等价的, 直到今天才明白: 并不等价!!!
class Store {
// 以下的structure1 / structure2 是等价的吗? 初看起来貌似等价
@observable
structure1 = { blocks: [0] };
structure2: any = observable({ blocks: [0] });
constructor() {
// 确实都打印出 Proxy, 看起来也没问题
console.log('structure1', this.structure1); => proxy
console.log('structure2', this.structure2); => proxy
// 这样就看出差异来了:
this.structure1 = { blocks: [2] };
console.log('1:', this.structure1.blocks); => proxy, ok!
this.structure2 = { blocks: [2] }; <== 注意这里的赋值方法与上面一模一样
console.log('2:', this.structure2.blocks); => [2], 非blocks!!!!
}
}
const store = new Store();
请仔细看上面的代码. 造成的差异就是: 整体赋值后, structure2 就变成了 普通对象!!!
补充:
奇怪的是: 如果你使用this.structure2.blocks = [2]
, 也就是属性赋值 而非整体赋值方式, 又不出会出现问题.
结论:
全部采用 @observable
方式
replace
方法与=
方法对于以下数组: @observable x = [{a:1}];
我们对其进行观察:
computed(()=>x).observe(change => console.log(...))
我们有三种方法对其进行修改: this.x.push({a:2}); <== 不会触发computed! this.x = [...x, {a:2}]; <== 会 触发computed! this.x.replace([...x, {a:2}]) <== 不会触发computed!
可以看到: 第1种和第3种做法, 不会触发computed
原因在于: push/replace
并没有改变x的引用
而=
却改变了x的引用, 自然就触发了 computed
由于我们一般不会直接使用 数组, 而是需要进一步加工, 比如取得 x[0]
所以这种做法本质上并没有差异
computed
中打断mobx proxy
引发的问题比如:
@computed
get xs() {
return [...this.xs]; 或 return this.xs.slice(0,2)
}
如果你直接打印 this.xs
, 会发现它是一个Proxy
但如果使用了 [...this.xs], 会发现它会输出: [1,2,3...]
对于computed
, 它会对比计算前后的值. 对于js:
[1,2,3] !== [1,2,3]
{a:1} !== {a:1}
所以会导致多余的渲染. 我们可以利用React.useMmeo消除之.
computed / autorun / reaction
使用注意事项autorun
: 只要对observable
进行赋值, 不管值有无改变, 都会触发reaction
/ computed
: 仅当值真正改变时才触发真实情况没这么简单.
由于在js中, {} !=={}, {a:1} !== {a:1}
, 所以会导致以下问题:
class X {
@observable
xs = [{}, 1];
constructor() {
this.disposer = computed(() => toJS(this.xs[0])).observe(change =>
console.log(change.oldValue, change.newValue); => {}, {}
);
}
}
const store = new X();
store.xs.push(2); <=== 会触发 computed!!!
computed
和reaction
正确响应很简单, 它们都有一个参数: equals, 带上 `{ equals: R.equals } 就行了 ( 具体查d.ts ) 这会保证 仅当它们的值发生变化时 才会调用
dispose
否则可能存在潜在bug
store
进行响应!我记得之前computed
可以跨store响应, 但不清楚为何现在不行, 有时间再仔细看下源码.
autorun
vscomputed
autorun: observable value有赋值, 则运行一次 ( 无论是否有变动 ) computed: observable value 有变动, 则运行一次
应尽量使用
computed