Mobx 如何缓存计算值?
Mobx how to cache computed values?
我正在使用 Mobx 构建 webgl 游戏引擎。我没有将它与反应一起使用。我用它来增强实体组件系统。我有实体 class 喜欢
import {observable, observe, computed, autorun} from 'mobx';
class Entity {
@observable position = [0,0,0]
@observable rotation = [0,0,0]
@computed get modelMat(){
return position * rotation;
}
}
我像这样使用这个实体:
var ent = new Entity();
entity.position = [0,10,0];
if(entity.modelMat == 6){
// do something
}
我的理解是直接阅读 modelMat
不是最佳做法。它会导致重新计算计算。它没有被缓存。这对我的游戏引擎是有害的,因为我可能会以 60fps 的速度访问这些计算值。
这对我来说似乎不直观,因为您使用 get
帮助程序定义计算,然后不应该将其用作 getter?调试 computedRequiresReaction
设置可用于防止这种直接计算读取模式。
configure({
computedRequiresReaction: true
});
那么我的问题是如何缓存或记忆这些将被频繁访问的计算值?为了避免这种情况,我开始使用一种使用自动运行的模式,以便在计算更改时更新局部变量。看起来像:
class Entity {
@observable position = [0,0,0]
@observable rotation = [0,0,0]
modelMat = []
constructor(){
autorun(() => {
this.modelMat = this.computedModelMat()
})
}
@computed get computedModelMat(){
return position * rotation;
}
}
这为 class 启用了一个接口,这样 ent.modelMat
仍然可以快速访问,但不会每次都重新计算。对此有更好的建议模式吗?为每个计算都有一个自动运行似乎是多余的。我的一些 classes 最终有许多自动运行处理程序来缓存这些值。
是的,您实际上使用的是推荐的方法:https://github.com/mobxjs/mobx/issues/356
as long as a computed
value is not used by a reaction
, it is not memoized and so it just like a normal eager evaluating function. If you would would use the [getter] in an autorun
this behavior will change and you won't see unnecessary computations.
...
the reason MobX works this way is that as long as a computed
value is not in use by some reaction
, it can simply be ignored. MobX doesn't recompute it all, and the computation doesn't keep any other computation alive.
但要提防memory leaks。问题中的代码没有泄漏,但我不确定你的所有代码:
const VAT = observable(1.2)
class OrderLine {
@observable price = 10
@observable amount = 1
constructor() {
// this autorun will be GC-ed together with the current orderline instance
this.handler = autorun(() => {
doSomethingWith(this.price * this.amount)
})
// this autorun won't be GC-ed together with the current orderline instance
// since VAT keeps a reference to notify this autorun,
// which in turn keeps 'this' in scope
this.handler = autorun(() => {
doSomethingWith(this.price * this.amount * VAT.get())
})
// So, to avoid subtle memory issues, always call..
this.handler()
// When the reaction is no longer needed!
}
}
基本上发生的事情是你正在走出 mobx world
而 mobx 不 关心它之外发生的事情。在 mobx
系统中,没有任何东西 观察 计算值,因此没有理由将其缓存(在内存中)。
这个问题没有很好的解决办法。
我能为您提供的最好的东西是在尝试按照您想要/需要的方式编写代码时提供更好的开发人员体验。
在下面的示例中,请注意 cacheComputed()
函数。它需要将实例和 属性 缓存为 string 并简单地将 autorun
包裹在 it.We 周围并在 constructor
中使用它class。此外,如果您要处理实例本身,请确保 dispose
of the autorun
。为此,我通常在实例上有一个 dispose()
方法来处理其中的所有反应。
当你完成所有反应时,你应该总是停止它们。
import { computed, autorun, observable, decorate } from "mobx";
function cacheComputed(instance, prop) {
return autorun(reaction => {
return instance[prop];
//todo - maybe throw if 'prop' does not exist
});
}
class A {
constructor() {
this.firstName = "Bob";
this.lastName = "Marley";
this.disposeFullName = cacheComputed(this, "fullName");
}
get fullName() {
console.log("computed");
return `${this.firstName} ${this.lastName}`;
}
dispose() {
this.disposeFullName();
}
}
decorate(A, {
firstName: observable,
lastName: observable,
fullName: computed
});
const a = new A();
console.log(a.fullName); //cached
console.log(a.fullName); //cached
console.log(a.fullName); //cached
//--- force recompute
console.log("---- recalculate computed");
a.lastName = "Dylan";
console.log(a.fullName); //recomputed
console.log(a.fullName); //cached
a.dispose(); // after this fullName will be recomputed always
上查看
注意 computed 支持 keepAlive
选项,这将强制 mobx 缓存值,即使没有观察者。而且它实际上比使用autorun观察更有效,因为有一些内部优化应用于此标志。
虽然有一点内存泄漏的风险:如果计算引用的任何内容仍然存在,则不会清除计算。但是,如果您仅指 class 本地事物,则应保存。
我正在使用 Mobx 构建 webgl 游戏引擎。我没有将它与反应一起使用。我用它来增强实体组件系统。我有实体 class 喜欢
import {observable, observe, computed, autorun} from 'mobx';
class Entity {
@observable position = [0,0,0]
@observable rotation = [0,0,0]
@computed get modelMat(){
return position * rotation;
}
}
我像这样使用这个实体:
var ent = new Entity();
entity.position = [0,10,0];
if(entity.modelMat == 6){
// do something
}
我的理解是直接阅读 modelMat
不是最佳做法。它会导致重新计算计算。它没有被缓存。这对我的游戏引擎是有害的,因为我可能会以 60fps 的速度访问这些计算值。
这对我来说似乎不直观,因为您使用 get
帮助程序定义计算,然后不应该将其用作 getter?调试 computedRequiresReaction
设置可用于防止这种直接计算读取模式。
configure({
computedRequiresReaction: true
});
那么我的问题是如何缓存或记忆这些将被频繁访问的计算值?为了避免这种情况,我开始使用一种使用自动运行的模式,以便在计算更改时更新局部变量。看起来像:
class Entity {
@observable position = [0,0,0]
@observable rotation = [0,0,0]
modelMat = []
constructor(){
autorun(() => {
this.modelMat = this.computedModelMat()
})
}
@computed get computedModelMat(){
return position * rotation;
}
}
这为 class 启用了一个接口,这样 ent.modelMat
仍然可以快速访问,但不会每次都重新计算。对此有更好的建议模式吗?为每个计算都有一个自动运行似乎是多余的。我的一些 classes 最终有许多自动运行处理程序来缓存这些值。
是的,您实际上使用的是推荐的方法:https://github.com/mobxjs/mobx/issues/356
as long as a
computed
value is not used by areaction
, it is not memoized and so it just like a normal eager evaluating function. If you would would use the [getter] in anautorun
this behavior will change and you won't see unnecessary computations....
the reason MobX works this way is that as long as a
computed
value is not in use by somereaction
, it can simply be ignored. MobX doesn't recompute it all, and the computation doesn't keep any other computation alive.
但要提防memory leaks。问题中的代码没有泄漏,但我不确定你的所有代码:
const VAT = observable(1.2) class OrderLine { @observable price = 10 @observable amount = 1 constructor() { // this autorun will be GC-ed together with the current orderline instance this.handler = autorun(() => { doSomethingWith(this.price * this.amount) }) // this autorun won't be GC-ed together with the current orderline instance // since VAT keeps a reference to notify this autorun, // which in turn keeps 'this' in scope this.handler = autorun(() => { doSomethingWith(this.price * this.amount * VAT.get()) }) // So, to avoid subtle memory issues, always call.. this.handler() // When the reaction is no longer needed! } }
基本上发生的事情是你正在走出 mobx world
而 mobx 不 关心它之外发生的事情。在 mobx
系统中,没有任何东西 观察 计算值,因此没有理由将其缓存(在内存中)。
这个问题没有很好的解决办法。
我能为您提供的最好的东西是在尝试按照您想要/需要的方式编写代码时提供更好的开发人员体验。
在下面的示例中,请注意 cacheComputed()
函数。它需要将实例和 属性 缓存为 string 并简单地将 autorun
包裹在 it.We 周围并在 constructor
中使用它class。此外,如果您要处理实例本身,请确保 dispose
of the autorun
。为此,我通常在实例上有一个 dispose()
方法来处理其中的所有反应。
当你完成所有反应时,你应该总是停止它们。
import { computed, autorun, observable, decorate } from "mobx";
function cacheComputed(instance, prop) {
return autorun(reaction => {
return instance[prop];
//todo - maybe throw if 'prop' does not exist
});
}
class A {
constructor() {
this.firstName = "Bob";
this.lastName = "Marley";
this.disposeFullName = cacheComputed(this, "fullName");
}
get fullName() {
console.log("computed");
return `${this.firstName} ${this.lastName}`;
}
dispose() {
this.disposeFullName();
}
}
decorate(A, {
firstName: observable,
lastName: observable,
fullName: computed
});
const a = new A();
console.log(a.fullName); //cached
console.log(a.fullName); //cached
console.log(a.fullName); //cached
//--- force recompute
console.log("---- recalculate computed");
a.lastName = "Dylan";
console.log(a.fullName); //recomputed
console.log(a.fullName); //cached
a.dispose(); // after this fullName will be recomputed always
上查看
注意 computed 支持 keepAlive
选项,这将强制 mobx 缓存值,即使没有观察者。而且它实际上比使用autorun观察更有效,因为有一些内部优化应用于此标志。
虽然有一点内存泄漏的风险:如果计算引用的任何内容仍然存在,则不会清除计算。但是,如果您仅指 class 本地事物,则应保存。