尽管 observables 没有改变,但 MobX 在读取时完全重新计算?
MobX full recompute on read though observables don't change?
我有一个看起来像
的 mobx 商店
class EntityStore {
rootStore
@observable entityIndex = {}
constructor(rootStore){
this.rootStore = rootStore;
}
@action addEntities(entityArray){
entityArray.forEach((entity) => this.addEntity(entity));
}
@action addEntity(entityProps){
if(entityProps.id && this.entityIndex[entityProps.id]){
throw new Error(`Failed to add ${entityProps.id} an entity with that id already exists`);
}
const entity = new Entity({
...entityProps,
assetStore: this.rootStore.assetStore,
regl,
});
this.entityIndex[entity.id] = entity;
}
@computed get entityAssetIds(){
return Object.values(this.entityIndex).map(e => e.assetId);
}
@computed get renderPayload(){
const payloadArray = [];
for(let entityId in this.entityIndex){
payloadArray.push(this.entityIndex[entityId].renderPayload)
}
return payloadArray;
}
}
这是一个昂贵的计算值,其子计算值在 requestAnimationFrame
循环中调用,该循环以 60fps 调用 entityStore.renderPayload()
。我需要将其缓存。
使用trace
我得到了输出
[mobx.trace] 'EntityStore@1.renderPayload' is being read outside a reactive context. Doing a full recompute
这让我感到惊讶,因为我的期望是 Mobx 只会在计算值的依赖可观察量发生变化时才重新计算。
有什么方法可以强制这种非重新计算行为吗?
更新:我没有使用反应。这是普通的 mobx 对象
我在 https://github.com/kevzettler/mobx_bad/ 创建了一个复制示例
看起来像
class ShouldMemoize {
@observable position = 0
staticValue = 200;
@computed get expensiveStaticOperation(){
console.log('this is expensive and should not recompute');
return this.staticValue*2;
}
@computed get output(){
return this.position + this.expensiveStaticOperation;
}
@action incrementPosition(val){
this.position+= 1;
}
}
let M = new ShouldMemoize();
console.log('**********start*********');
setInterval(() =>{
M.incrementPosition();
console.log(
"*tick*",
M.output
);
}, 60)
此示例演示了从另一个计算方法 output
引用的计算方法 expensiveStaticOperation
。 output
方法被快速调用,日志输出表明 expensiveStaticOperation
然后也被调用并在每个滴答时重新计算。我希望因为 expensiveStaticOperation
的依赖值不会改变,所以它会被记忆而不是重新执行。
这通常是您尝试在不使用 observer
(或 reaction
或 autorun
)的情况下访问该值时出现的行为。您没有提及您是否使用 React 或其他库,也没有解释您如何访问该值。可能值得在 CodeSandbox 或类似的网站上设置一个最小的示例来重现您所看到的问题。
如果我不得不推测,我会说您正在某个组件中的某处使用您的计算值,该组件未用 observer
包装(在 React 的情况下)。 Here's 一个在没有额外框架的情况下重现问题的示例(删除了未使用的方法):
import { autorun, computed, configure, observable } from "mobx";
class EntityStore {
@observable entityIndex = {};
@computed get renderPayload() {
const payloadArray = [];
for (let entityId in this.entityIndex) {
payloadArray.push(this.entityIndex[entityId].renderPayload);
}
return payloadArray;
}
}
configure({ computedRequiresReaction: true });
const store = new EntityStore();
// console.log('THIS WILL TRIGGER A WARNING:', store.renderPayload)
autorun(() => console.log('THIS WILL NOT TRIGGER A WARNING:', store.renderPayload))
如果取消注释日志,您应该会看到控制台警告。很有可能,您正在 observer
或类似的地方之外的某个地方使用计算值。如果您确定情况并非如此,也许可以在问题中添加更多细节。
Mobx 并不像您在 repo 中用示例命名它那样糟糕,相反,它非常棒,不会重新计算任何代码未观察到的那些值。
要修复您的示例,只需将这一行放在 setInterval
之前
observe(M, 'output', () => {})
现在 MobX 知道输出被观察到,你昂贵的计算只会 运行 一次。直到一些相关的变量发生变化。
只需在开始动画循环之前观察使用过的计算字段,并在结束后进行处理,它会非常有效。
我有一个看起来像
的 mobx 商店class EntityStore {
rootStore
@observable entityIndex = {}
constructor(rootStore){
this.rootStore = rootStore;
}
@action addEntities(entityArray){
entityArray.forEach((entity) => this.addEntity(entity));
}
@action addEntity(entityProps){
if(entityProps.id && this.entityIndex[entityProps.id]){
throw new Error(`Failed to add ${entityProps.id} an entity with that id already exists`);
}
const entity = new Entity({
...entityProps,
assetStore: this.rootStore.assetStore,
regl,
});
this.entityIndex[entity.id] = entity;
}
@computed get entityAssetIds(){
return Object.values(this.entityIndex).map(e => e.assetId);
}
@computed get renderPayload(){
const payloadArray = [];
for(let entityId in this.entityIndex){
payloadArray.push(this.entityIndex[entityId].renderPayload)
}
return payloadArray;
}
}
这是一个昂贵的计算值,其子计算值在 requestAnimationFrame
循环中调用,该循环以 60fps 调用 entityStore.renderPayload()
。我需要将其缓存。
使用trace
我得到了输出
[mobx.trace] 'EntityStore@1.renderPayload' is being read outside a reactive context. Doing a full recompute
这让我感到惊讶,因为我的期望是 Mobx 只会在计算值的依赖可观察量发生变化时才重新计算。
有什么方法可以强制这种非重新计算行为吗?
更新:我没有使用反应。这是普通的 mobx 对象 我在 https://github.com/kevzettler/mobx_bad/ 创建了一个复制示例 看起来像
class ShouldMemoize {
@observable position = 0
staticValue = 200;
@computed get expensiveStaticOperation(){
console.log('this is expensive and should not recompute');
return this.staticValue*2;
}
@computed get output(){
return this.position + this.expensiveStaticOperation;
}
@action incrementPosition(val){
this.position+= 1;
}
}
let M = new ShouldMemoize();
console.log('**********start*********');
setInterval(() =>{
M.incrementPosition();
console.log(
"*tick*",
M.output
);
}, 60)
此示例演示了从另一个计算方法 output
引用的计算方法 expensiveStaticOperation
。 output
方法被快速调用,日志输出表明 expensiveStaticOperation
然后也被调用并在每个滴答时重新计算。我希望因为 expensiveStaticOperation
的依赖值不会改变,所以它会被记忆而不是重新执行。
这通常是您尝试在不使用 observer
(或 reaction
或 autorun
)的情况下访问该值时出现的行为。您没有提及您是否使用 React 或其他库,也没有解释您如何访问该值。可能值得在 CodeSandbox 或类似的网站上设置一个最小的示例来重现您所看到的问题。
如果我不得不推测,我会说您正在某个组件中的某处使用您的计算值,该组件未用 observer
包装(在 React 的情况下)。 Here's 一个在没有额外框架的情况下重现问题的示例(删除了未使用的方法):
import { autorun, computed, configure, observable } from "mobx";
class EntityStore {
@observable entityIndex = {};
@computed get renderPayload() {
const payloadArray = [];
for (let entityId in this.entityIndex) {
payloadArray.push(this.entityIndex[entityId].renderPayload);
}
return payloadArray;
}
}
configure({ computedRequiresReaction: true });
const store = new EntityStore();
// console.log('THIS WILL TRIGGER A WARNING:', store.renderPayload)
autorun(() => console.log('THIS WILL NOT TRIGGER A WARNING:', store.renderPayload))
如果取消注释日志,您应该会看到控制台警告。很有可能,您正在 observer
或类似的地方之外的某个地方使用计算值。如果您确定情况并非如此,也许可以在问题中添加更多细节。
Mobx 并不像您在 repo 中用示例命名它那样糟糕,相反,它非常棒,不会重新计算任何代码未观察到的那些值。
要修复您的示例,只需将这一行放在 setInterval
observe(M, 'output', () => {})
现在 MobX 知道输出被观察到,你昂贵的计算只会 运行 一次。直到一些相关的变量发生变化。
只需在开始动画循环之前观察使用过的计算字段,并在结束后进行处理,它会非常有效。