Vue.js-花费太多时间来“破坏”组件

高田悠*_*高田悠 6 javascript performance vue.js

1我有一个用vue.js创建的时间表组件,它包括约200个子时间轴组件(以嵌套形式)(我想上传图像,但不能没有10个声誉)。

现在的问题是,销毁该组件需要花费6秒钟以上的时间。

Chrome表示“删除”功能(每次我们销毁组件时都会由vue.js调用)被调用了很多次,每个功能大约需要20到40毫秒。

vue.js的删除功能如下所示:

function remove (arr, item) {
  if (arr.length) {
    var index = arr.indexOf(item);
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

似乎第一个参数arr是几个VueComponents或超过2000个Watcher对象。

现在,我的问题是:1.在这种情况下,“观察者”是什么,为什么它的数量超过2000?2.为什么我不处理大约10000个组件,却要花这么长时间?

我想这是vue.js规范的问题,但是如果您有类似的问题或对此有任何想法,请帮助我。谢谢!

在此处输入图片说明 上面是时间线组件的显示方式,灰色背景面板和紫色背景面板(带有男人图标)都是子组件。当您单击紫色面板时,vue-router会路由到详细信息页面,并且那时所有组件都被销毁(也就是说,发生上述问题时)

ema*_*rbo 6

我们遇到过类似的问题,发现它们都有一个共同的潜在问题:依赖同一个响应式对象的组件太多。以下是可能影响任何项目的 3 种主要情况:

  • 许多router-link组件
  • 安装 Vue I18n 时的许多组件(任何类型)
  • 许多组件直接访问 Vuex 存储的渲染或计算属性。

我们的方法是避免访问渲染和计算属性函数上的共享反应对象。相反,将它们作为props(反应性)传递或在createdupdated钩子(非反应性)上访问它们以存储在组件的$data. 阅读下文了解更多详细信息以及 3 个案例中的每一个。

Vue 2 反应性的简要说明

(不需要的可以跳过)

Vue 反应性基本上依赖于两个相互交织的对象:WatcherDep。Watchers在deps属性中有一个依赖列表(Deps),Deps在属性中有一个依赖列表(Watchers)subs

对于每个响应式事物,Vue 都会实例化一个 Dep 来跟踪对它的读取和写入。

Vue 为每个组件(实际上是render函数)和每个计算属性实例化一个 Watcher 。观察者在执行过程中观察函数。在watch 时,如果读取了一个响应式对象,关联的 Dep 会注意到 Watcher,并且它们变得相关:Watcher.deps包含Dep,和Dep.subs包含Watcher

之后,如果反应性事物发生变化,则关联Dep 通知其所有依赖项 ( Dep.subs) 并告诉他们更新 ( Watcher.update)。

当一个组件被销毁时,它的所有 Watcher 也会被销毁。这个过程意味着迭代每个Watcher.deps以从Dep.subs(参见Watcher.teardown)中删除 Watcher 本身。

问题

所有依赖于同一个反应性事物的组件都在同一个Dep.subs. 在以下示例中,它Dep.subs包含 10,000 个观察者:

  • 渲染的 1,000 个项目(例如网格、无限滚动,...)
  • 每个项目都包含 10 个组件:本身、2 个路由器链接、3 个按钮、4 个其他(嵌套和非嵌套,来自您的代码或第三方)。
  • 所有组件都依赖于同一个反应对象。

销毁页面时,10,000 个观察者会将自己从Dep.subs数组中移除(一个一个)。除去本身的成本10k * O(10k - i),其中i已被移除观察家的数量。

一般来说,移除n物品的成本是O((n^2)/2)

解决方法

如果您渲染许多组件,请避免访问render或 计算属性的共享反应依赖项。

相反,将它们作为props或访问它们的createdupdated钩子并将它们存储在组件的$data. 请记住,钩子不会被监视,因此如果数据源发生变化,组件将不会更新,这仍然适用于许多情况(安装组件后数据不会更改的任何情况)。

如果您的页面呈现一长串项目,vue-virtual-scroller肯定会有所帮助。在这种情况下,您仍然可以访问共享的反应式依赖项,因为 vue-virtual-scroller 重用了一小部分组件(它不会呈现看不见的东西)。

考虑到拥有数千个组件可能比您预期的要容易,因为我们倾向于编写小组件并组合它们(实际上是一种很好的做法)

案例:Vuex

如果你在你的 render o 计算属性中做这样的事情,你的组件依赖于所有反应性事物链:state, account, profile

function myComputedProperty() {
    this.$store.state.account.profile.name;
}
Run Code Online (Sandbox Code Playgroud)

在这个例子中,如果你的帐户在组件挂载后没有改变,你可以从createdorbeforeMount钩子中读取它并将其存储name在 Vue 上$data。由于这不是渲染函数的一部分,也不是计算属性的一部分,因此没有观察者监视对存储的访问。

function beforeMount() {
    this.$data.userName = this.$store.state.account.profile.name;
}
Run Code Online (Sandbox Code Playgroud)

案例:路由器链接

问题#3500

案例:Vue I18n

这具有相同的潜在问题,但解释略有不同。见问题#926

  • 很棒的描述,对我很有帮助,谢谢! (2认同)

Ant*_*nov 2

这不是 vue 问题,请参阅您的 mixins/options。
例如。i18n(我的痛苦)200 个组件中的每一个都会显示相同的结果。它消除了很多关注者beforeDestroy。如果没有i18n列表,工作速度会快 30 倍。
如何修复它?将慢速钩子处理程序移至父组件并从中获取所需的数据/方法。

样品与i18n

Vue.mixin({
    beforeCreate() {
        if (this.$options.useParentLocalization) {
            this._i18n = parent.$i18n;
        }
    },
});
Run Code Online (Sandbox Code Playgroud)

用法:

new Vue({
  // i18n, <-- before
  useParentLocalization: true,
  components: {
    Component1
  }
})
Run Code Online (Sandbox Code Playgroud)