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, который вызывает entityStore.renderPayload() со скоростью 60 кадров в секунду. Мне нужно, чтобы это было кэшировано.

используя trace, я получил результат [mobx.trace] '[email protected]' 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)

В этом примере демонстрируется вычисляемый метод expensiveStaticOperation, на который ссылается другой вычисляемый метод output. метод output вызывается через короткие промежутки времени, и вывод журнала указывает, что затем также вызывается expensiveStaticOperation и пересчитывается на каждом такте. Я ожидаю, что, поскольку зависимые значения expensiveStaticOperation не меняются, они будут запоминаться, а не выполняться повторно.


person kevzettler    schedule 20.06.2019    source источник


Ответы (2)


Обычно такое поведение наблюдается, когда вы пытаетесь получить доступ к значению без использования observer (или reaction, или autorun). Вы не упоминаете, используете ли вы 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 или чего-то подобного. Если вы уверены, что это не так, возможно, добавьте в вопрос больше деталей.

person Rich Churcher    schedule 20.06.2019
comment
Было бы очень полезно, если бы вы могли опубликовать полный минимальный пример, который воспроизводит вашу проблему. - person Rich Churcher; 20.06.2019
comment
Я добавил минимальный пример и щедрость - person kevzettler; 20.01.2020
comment
Спасибо, попробую через пару часов еще раз посмотреть. - person Rich Churcher; 21.01.2020

Mobx не так уж плох, как вы назвали его в репозитории с примером, вместо этого он настолько хорош, что не пересчитывает те значения, которые не наблюдаются ни в одном коде.

Чтобы исправить свой пример, просто поместите эту строку перед setInterval

observe(M, 'output', () => {})

Теперь MobX знает, что выходные данные наблюдаются, и ваши дорогостоящие вычисления будут выполняться только один раз. Пока не изменится какая-то связанная переменная.

Просто наблюдайте за используемыми вычисляемыми полями перед запуском цикла анимации и утилизируйте после завершения, это будет работать как шарм.

person user2541867    schedule 27.01.2020