Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MobX performance when replacing observable data

I need to replace data in my observable object when I get a new dump from the socket:

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

However, I noticed performance hits when the data object scales, probably because MobX will trigger reactions everywhere even if some properties/values are identical.

Is there a "smarter" way? - I’m thinking that f.ex only replacing the affected parts of the object would be better than replacing the entire observable?

I made a small demo here explaining what I mean: https://jsfiddle.net/yqqxokme/. Replacing the object causes new reactions, even if the data is exactly the same (expected). But I’m sure there is a way to only mutate the affected parts of the data object like in the merge() function.

like image 476
David Hellsing Avatar asked Apr 24 '18 11:04

David Hellsing


1 Answers

So here are few things and cases. I have changed the dump function to below to simulate changes

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])
}

Using extendObservable

Now if you use below code

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

And run it through the dump cycle, the output is below

Output 1

The event for bar won't start raising until you get a change to foo, which happens on below change

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

Outcome: New keys can only be observed existing observable keys change

Using map

In this we change the code like below

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

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

Output 2

Outcome: The data is merge only and won't get deletions. You also will get duplicate events as it is a merge only option

Using Object Diff

You can use an object diff library like below

https://github.com/flitbit/diff

You can update the code like below

  @observable data = {
    foo: 'bar'
  }

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

Output 3

Outcome: The events only happen when data change and not on re-assignment to same object

Using Diff and Applying Diff

Using the same library we gave used earlier, we can apply just the changes needed

If we change the code like below

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

If run the above, we get following output

Output 4

Outcome: Only changes to initial set of keys is observed, give you don't delete those in keys in between

It also gives you diff in below format

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

Which means you can have better control of things based on field names when you want

Below is the fiddle that I used, most code commented but in case you need to look at the imports use below

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

like image 146
Tarun Lalwani Avatar answered Oct 04 '22 15:10

Tarun Lalwani