Производительность MobX при замене наблюдаемых данных

Мне нужно заменить данные в моем наблюдаемом объекте, когда я получаю новый дамп из сокета:

class Store {
    @observable data = { foo: 'bar' }
    replaceFromDump(newData) {
        this.data = newData
    }
}
const store = new Store()
store.replaceFromDump({ foo: 'bar' })

// { foo: 'bar' } can be a huge amount of JSON

Тем не менее, я заметил образы производительности, когда объект данных масштабируется, вероятно, потому, что MobX будет вызывать реакции везде, даже если некоторые свойства/значения идентичны.

Есть ли более "умный" способ? - Я думаю, что f.ex заменяет только затронутые части объекта лучше, чем замена всего наблюдаемого?

Я сделал небольшую демонстрацию здесь, объясняя, что я имею в виду: https://jsfiddle.net/yqqxokme/. Замена объекта вызывает новые реакции, даже если данные точно такие же (ожидается). Но я уверен, что есть способ только изменить затронутые части объекта данных, как в функции merge().

Ответ 1

Итак, вот несколько вещей и случаев. Я изменил функцию дампа ниже, чтобы имитировать изменения

variations = [
  {foo: 'bar'},
  {foo: 'bar'},
  {foo: 'bar2' },
  {foo: 'bar2' },
  {foo: 'bar2', bar: {name: "zoo"} },
  {foo: 'bar2', bar: {name: "zoo"} },
  {foo: 'bar2', bar: {name: "zoo2"} },
  {foo: 'bar2', bar: {name: "zoo2"} },
  {foo: 'barnew', bar: {name: "zoo2", new: "yes"} },
  {foo: 'barnew', bar: {name: "zoo2", new: "no"} },
  {foo: 'barnew', bar: {name: "zoo2", new: "no"} }
]

i=0;

dump = () => {
  i++;
  i = i%variations.length;
  console.log("Changing data to ", variations[i]);
    store.replaceFromDump(variations[i])
}

Использование extendObservable

Теперь, если вы используете код ниже

replaceFromDump(newData) {
  extendObservable(this.data, newData)
}

И запустите его через цикл дампа, выход ниже

Output 1

Событие для bar не начнется, пока вы не получите изменения в foo, что происходит при изменении ниже

{foo: 'barnew', bar: {name: "zoo2", new: "yes"} },

Результат. Новые клавиши можно наблюдать только при изменении существующих наблюдаемых клавиш.

Использование карты

В этом случае мы изменим код, как показано ниже.

  @observable data = map({
    foo: 'bar'
  })

replaceFromDump(newData) {
  this.data.merge(newData)
}

Output 2

Результат: данные объединяются и не будут удаляться. Вы также будете получать повторяющиеся события, так как это вариант слияния

Использование объекта Diff

Вы можете использовать библиотеку объектов diff, как показано ниже.

https://github.com/flitbit/diff

Вы можете обновить код, как показано ниже.

  @observable data = {
    foo: 'bar'
  }

replaceFromDump(newData) {
    if (diff(mobx.toJSON(this.data), newData)){
        this.data = newData;
    } 
}

Output 3

Результат: события происходят только при изменении данных, а не при повторном назначении на один и тот же объект

Использование Diff и применение Diff

Используя ту же библиотеку, которую мы использовали ранее, мы можем применить только необходимые изменения

Если мы изменим код, как показано ниже

replaceFromDump(newData) {
    observableDiff(toJSON(this.data), newData, d => {
          applyChange(this.data, newData, d);
    })
  } 

Если запустить выше, мы получим следующий вывод

Output 4

Итог: Наблюдается только изменение начального набора ключей, чтобы вы не удаляли их в клавишах между

Он также дает вам разницу в формате ниже

{"kind":"E","path":["foo"],"lhs":"bar2","rhs":"barnew"}
{"kind":"N","path":["bar","new"],"rhs":"yes"}

Это означает, что вы можете лучше контролировать вещи, основанные на именах полей, когда захотите

Ниже приведен скрипт, который я использовал, большинство кодов прокомментировано, но если вам нужно взглянуть на использование импорта ниже

https://jsfiddle.net/tarunlalwani/fztkezab/1/

Ответ 2

Использование extendObservable предотвратит реакцию обжига, если значения идентичны:

class Store {
    @observable data = { foo: 'bar' }
    replaceFromDump(newData) {
        extendObservable(this.data, newData)
    }
}
const store = new Store()
store.replaceFromDump({ foo: 'bar' })

Ответ 3

Вы можете прочитать об оптимизации ваших компонентов: https://mobx.js.org/best/react-performance.html

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

React отображает все дочерние компоненты, которые используют реквизиты, которые изменились.

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

@observable data = {}

@action
replaceChanges(partialNewData) {
    Object.keys(partialNewData).forEach(propName => {
       this.data[propName] = partialNewData[propname];
   }
}

Mobx не проверяет, является ли измененное состояние актуальным. таким образом, даже изменение состояния с одним и тем же объектом может вызвать повторную визуализацию. (https://mobx.js.org/best/react.html)

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

Если вы правильно напишете свой код (например: не используйте выражения labmda в методе реагирования рендеринга), ваш код должен быть рендеринга довольно эффективно.

Ответ 4

Вы всегда создаете новый объект на этой линии и передаете его функции

store.replaceFromDump({ foo: 'bar' })

Два объекта, даже с одинаковыми парами ключ/значение, не вернут true из этого типа оператора if

if( object1 === object2 )

Таким образом, эта функция работает так, как предполагалось, и как это следует делать, если принять это.

Вы можете убедиться, что если данные изменились таким образом.

  replaceFromDump(newData) {
    if( JSON.stringify( this.data ) !== JSON.stringify( newData ) ){
        this.data = newData
    }
  }

Затем я использовал React.PureComponent для ваших классов, которые вы выполняете рендеринг, в которых вы должны избавиться от дополнительных/неинтенсивных рендерингов. Поэтому просто расширяйте свои классы от них

class IRenderMobX extends React.PureComponent

React.PureComponent похож на React.Component. Разница между ними заключается в том, что React.Component не реализует shouldComponentUpdate(), но React.PureComponent реализует его с помощью неглубокой поддержки и сравнения состояний.

Если ваша функция React components render() отображает тот же результат при одинаковых реквизитах и состоянии, вы можете использовать React.PureComponent для повышения производительности в некоторых случаях.

По-моему, я не думаю, что вы достигли каких-либо замечательных преимуществ при реализации метода merge() типа мутации. Ваш PureComponent должен быть в состоянии узнать себя, если они должны повторно отобразить или не сделать, сделав неглубокую проверку и сравнение состояний, и это не должно быть вашей проблемой, о которой нужно беспокоиться.