MobX 中文文档

  开始纠错

(@)computed

计算值函数是通过当前状态或者其他计算值衍生出来的。理论上,它们和Excel中的公式非常类似。 请重视计算值,它们会帮助你减少需要修改的状态。因为它是经过高度优化的,所以请尽可能地使用他们。

无需为 computedautorun 的使用感到困惑,虽然他们都是一种状态改变的响应。 computed 是你希望生成一个新的 时所使用的。 autorun 则是希望产生一些副作用时所使用的,例如记录日志,发送请求等。

计算值是通过所有会影响它的状态所自动衍生的。计算值在大部分情况下都是经过优化的,所以应尽可能地使用纯函数(同样输入永远产生同样输出)。一个计算值不会重新执行,而是是用缓存,只要它所依赖的状态没有变化。如果一个计算值没有被其他计算值所使用或者被观察,也不会重新执行。

这一套机制非常方便,甚至当一个计算值不再使用时,MobX 可以自动进行垃圾回收,这和你必须手动处理的 autorun 不同。

有些时候,这会使刚开始使用 MobX 的人们感到疑惑,如果你创建了一个计算值,但在任何地方都没有使用,则计算值不会缓存它的值或重计算。如果你希望强制计算值更新,你可以使用 observe 或者 keepAlive.

注意 computed 是不可枚举的,也不能被继承链覆盖。

@computed

如果你 能使用装饰器 你可以在任何类属性getter使用 @computed 装饰器来声明这是一个计算值。

import {observable, computed} from "MobX";

class OrderLine {
    @observable price = 0;
    @observable amount = 1;

    constructor(price) {
        this.price = price;
    }

    @computed get total() {
        return this.price * this.amount;
    }
}

computed 修饰符

如果你的环境不支持装饰器。请和extendObservable / observable 一起,使用 computed(expression) 修饰方法给可观察对象增加一个新的计算值。

@computed get propertyName() { } 只是类创建时调用 extendObservable(this, { propertyName: get func() { } }) 的一个基本语法糖.

import {extendObservable, computed} from "MobX";

class OrderLine {
    constructor(price) {
        extendObservable(this, {
            price: price,
            amount: 1,
            // valid:
            get total() {
                return this.price * this.amount
            },
            // also valid:
            total: computed(function() {
                return this.price * this.amount
            })
        })
    }
}

Setters 计算值

如果需要创建一个setter类型的计算值,注意这些setter不能直接改变计算值的值。但他们可用于衍生的相反用途,例如:

const box = observable({
    length: 2,
    get squared() {
        return this.length * this.length;
    },
    set squared(value) {
        this.length = Math.sqrt(value);
    }
});

或者类似的

class Foo {
    @observable length: 2,
    @computed get squared() {
        return this.length * this.length;
    }
    set squared(value) { //this is automatically an action, no annotation necessary
        this.length = Math.sqrt(value);
    }
}

注意:永远在getter之后使用setter,否则一些TypeScript的版本会将同样的命名声明为两个属性

注意: setters 需要 MobX 2.5.1 以上版本

computed(expression) 作为函数

computed 可以像函数一样被调用。就像 observable.box(primitive value) 创建了一个独立的被观察者。 使用 .get() 获取当前的计算值,或者使用 .observe(callback) 观察它的变化。 这个 computed 的形式并不常使用,但是在一些你需要传递一个"boxed"的计算值时,可能会非常有用。

例如:

import {observable, computed} from "MobX";
var name = observable("John");

var upperCaseName = computed(() =>
    name.get().toUpperCase()
);

var disposer = upperCaseName.observe(change => console.log(change.newValue));

name.set("Dave");
// prints: 'DAVE'

computed 的选项

当将 computed 作为一个修饰方法或者box使用时,它接受第二个配置选项,你可以传入下面这些选项参数。

  • name: 字符型, debug时devtools或spy使用的debug name。
  • context: 上下文
  • setter: 这个参数是需要传入的。如果没有setter函数,则不能赋予计算值一个新的值。如果第二个配置选项传入一个函数,则被认为是setter函数。
  • compareStructural: 默认值为 false. 当设置为true时,computed 函数在输出之前会比较当前值与之前值结构上是否有变化。例如当将a.sender替换为a.cloneSender时,依旧会触发 computed 变化,当在一些点、向量或者颜色结构的场景下非常有用。

@computed.struct 用于结构比较

@computed 不带入任何变量,如果你想创建一个结构比较的计算值,请使用 @computed.struct

注意错误处理

如果一个计算值在计算的过程中抛出一个错误,这个错误会被捕捉并且在计算值被使用时抛出。抛出错误并不中断追踪,所以程序可以从报错中恢复运行。

例如:

const x = observable(3)
const y = observable(1)
const divided = computed(() => {
    if (y.get() === 0)
        throw new Error("Division by zero")
    return x.get() / y.get()
})

divided.get() // returns 3

y.set(0) // OK
divided.get() // 抛出: Division by zero
divided.get() // 抛出: Division by zero

y.set(2)
divided.get() // 恢复; Returns 1.5