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;
}
}

这是一个昂贵的计算值,其中包含子计算值,在以 60fps 调用entityStore.renderPayload()requestAnimationFrame循环中调用。我需要缓存它。

使用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)

此示例演示从另一个计算方法引用的计算方法expensiveStaticOperationoutputoutput方法以快速间隔调用,日志输出指示随后也会调用expensiveStaticOperation并在每次逐笔报价时重新计算。我希望因为expensiveStaticOperation的依赖值不会改变,所以它会被记住而不是重新执行。

这通常是当您尝试在不使用observer(或reactionautorun)的情况下访问值时看到的行为。你没有提到你使用的是 React 还是其他库,也没有解释你是如何访问这个值的。可能值得在 CodeSandbox 或类似设备上设置一个最小示例来重现您所看到的问题。

如果我不得不推测,我会说你正在一个没有用observer包装的组件中的某个地方使用你的计算值(在 React 的情况下)。下面是一个示例(删除了未使用的方法),无需其他框架即可重现问题:

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 知道输出是被观察到的,并且您昂贵的计算将只运行一次。直到一些相关的变量发生变化。

只需在开始动画循环之前观察使用的计算字段,并在结束后进行处置,它就会像魅力一样工作。

最新更新