【问题标题】:MobX performance when replacing observable data替换可观察数据时的 MobX 性能
【发布时间】:2018-04-24 11:23:55
【问题描述】:

当我从套接字获得新的转储时,我需要替换可观察对象中的数据:

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 只替换对象的受影响部分会比替换整个 observable 更好?

我在这里做了一个小演示来解释我的意思:https://jsfiddle.net/yqqxokme/。 替换对象会引起新的反应,即使数据完全相同(预期)。但我确信有一种方法可以只改变数据对象的受影响部分,就像在 merge() 函数中一样。

【问题讨论】:

  • 你试过extendObservable吗?
  • 由于数据对象本身曾经在客户端修改过自己,还是总是作为一个整体使用/替换?在后一种情况下,您可以使用 @observable.ref 避免递归创建可观察对象,另请参阅修饰符文档
  • @mweststrate 数据仅在第一次和重新连接时作为转储出现,然后在我们合并到现有数据对象的应用程序生命周期中以小块形式出现。但是只有在对现有(过时的)可观察数据注入新转储时,性能损失才会明显
  • @David,感谢您对答案的反馈,因为赏金即将到期

标签: javascript reactjs mobx


【解决方案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)
}

并通过转储循环运行它,输出如下

bar 的事件在您更改为 foo 之前不会开始引发,这发生在下面的更改中

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

结果:新键只能观察到现有可观察键的变化

使用地图

在此我们更改如下代码

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

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

结果:数据只合并,不会被删除。您还将获得重复的事件,因为它是仅合并选项

使用对象差异

您可以使用如下所示的对象差异库

https://github.com/flitbit/diff

你可以像下面这样更新代码

  @observable data = {
    foo: 'bar'
  }

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

结果:事件仅在数据更改时发生,而不是在重新分配给同一对象时发生

使用差异和应用差异

使用我们之前提供的相同库,我们可以应用所需的更改

如果我们像下面这样更改代码

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

如果运行上面的,我们会得到以下输出

结果:只观察到初始键集的变化,不要删除它们之间的键

它还为您提供以下格式的差异

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

这意味着您可以在需要时根据字段名称更好地控制事物

以下是我使用的小提琴,大多数代码都已注释,但如果您需要查看下面的导入使用

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

【讨论】:

    【解决方案2】:

    您可以阅读有关优化组件的信息: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)

    是的,正如您所说:您还可以仅针对已更改的属性在旧状态上深度合并/覆盖新状态。这也会触发更少的重新渲染。

    如果您正确编写代码(例如:不要在您的 react 渲染方法中使用 labmda 语句),您的代码应该会非常有效地重新渲染。

    【讨论】:

    • lambda 语句并不慢(如果你在一个渲染方法中没有 1000 个)
    • @havenchyk 它们并不慢,但它们确实会触发重新渲染(在子组件中),因为它们是在每个渲染方法上重新创建的。
    • 没错,但我建议作者先测量
    • 谢谢,我知道优化 React 组件,但这更多的是 MobX 用例。看这个演示我对替换和修改的意思:jsfiddle.net/yqqxokme
    • 将其设置为另一个字符串有效,显然它会查看字符串是否等于并且不会触发。但是替换整个对象时,内存引用会发生变化,因此即使对象相同,也会始终触发:jsfiddle.net/yqqxokme/1
    【解决方案3】:

    如果值相同,使用 extendObservable 将防止反应触发:

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

    【讨论】:

    • 如果添加了新属性并且它们发生了变化,这将不起作用,不会生成任何事件
    【解决方案4】:

    你总是在这一行上创建一个新对象并将其传递给函数

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

    即使具有相同的键/值对的两个对象也不会从这种 if 语句中返回 true

    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 通过浅 prop 和 state 比较来实现它。

    如果你的 React 组件的 render() 函数在给定相同的 props 和 state 的情况下呈现相同的结果,你可以在某些情况下使用 React.PureComponent 来提升性能。

    在我看来,我认为实现merge() 类型的突变行为不会带来任何显着的性能优势。你的 PureComponent 应该能够通过浅层 prop 和 state 比较来知道他们是否应该重新渲染,这不应该是你担心的问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多