You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Mobx计算属性同时实现Lazy与Cached特性?

实现具备懒加载缓存特性的MobX自动观察工具函数

问题描述

现有如下MobX类示例:

class OrderLine {
    price = 0
    amount = 1

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

    get total() {
        console.log("Computing...")
        return this.price * this.amount
    }
}

当前total计算属性仅在被autorun调用并访问时才会被缓存。但实际需求希望计算属性同时具备**懒加载(Lazy)和缓存(Cached)**特性:仅在第一次实际读取(引用)时执行计算并缓存,同时避免autorun提前触发计算。

需要实现一个类似makeAutoObservableLazyCached的工具函数,让计算属性无需提前读取值即可完成缓存,同时支持选择性装饰特定计算属性。初始函数框架如下:

const makeAutoObservableLazyCached = (target) => {
  makeAutoObservable(target)
  // 需要补充的逻辑
}

解决方案

方案1:全局处理所有计算属性

核心思路是遍历对象属性,识别计算属性并重写其getter,手动管理缓存状态和依赖跟踪,跳过MobX默认的提前计算逻辑:

import { makeAutoObservable, observable, computed } from "mobx"

const makeAutoObservableLazyCached = (target) => {
  makeAutoObservable(target)

  Object.keys(target).forEach(key => {
    const descriptor = Object.getOwnPropertyDescriptor(target, key)
    // 判断是否为计算属性
    if (descriptor && typeof descriptor.get === 'function' && !descriptor.writable) {
      const originalGetter = descriptor.get
      let cache = { value: undefined, computed: false }
      const dependencies = observable.box(true)

      Object.defineProperty(target, key, {
        get() {
          // 未计算或依赖变化时重新计算
          if (!cache.computed || dependencies.get()) {
            cache.value = originalGetter.call(target)
            cache.computed = true
            dependencies.set(false)
          }
          // 跟踪依赖变化,标记缓存失效
          computed(() => {
            originalGetter.call(target)
            dependencies.set(true)
          })
          return cache.value
        },
        enumerable: descriptor.enumerable,
        configurable: descriptor.configurable
      })
    }
  })

  return target
}

使用时直接替换原makeAutoObservable即可,所有计算属性都会自动启用懒加载缓存。

方案2:选择性装饰特定计算属性

如果只需对部分计算属性生效,可以自定义装饰器实现精准控制:

import { makeAutoObservable, observable, computed } from "mobx"

// 自定义懒加载缓存装饰器
const lazyCached = (target, key, descriptor) => {
  const originalGetter = descriptor.get
  let cache = { value: undefined, computed: false }
  const dependencies = observable.box(true)

  descriptor.get = function() {
    if (!cache.computed || dependencies.get()) {
      cache.value = originalGetter.call(this)
      cache.computed = true
      dependencies.set(false)
    }
    computed(() => {
      originalGetter.call(this)
      dependencies.set(true)
    })
    return cache.value
  }

  return descriptor
}

// 保持原makeAutoObservable逻辑的包装函数
const makeAutoObservableLazyCached = (target) => {
  return makeAutoObservable(target)
}

使用时给需要的计算属性添加装饰器即可:

class OrderLine {
    price = 0
    amount = 1

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

    @lazyCached
    get total() {
        console.log("Computing...")
        return this.price * this.amount
    }

    // 其他计算属性保持默认行为
    get tax() {
        return this.total * 0.1
    }
}

原理说明

  • 手动维护缓存状态:通过cache变量记录计算结果和计算状态,仅在第一次读取或依赖变化后触发重新计算
  • 依赖跟踪:用computed包裹原始getter,当依赖项变更时标记缓存失效,保证下次读取能获取最新值
  • 避免提前计算:跳过MobX默认的计算属性自动触发逻辑,仅在实际读取时执行计算

内容的提问来源于stack exchange,提问作者404

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 11:00:02