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

时间:2019-04-25 06:18:15

标签: 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)
    }
  }
}

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

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

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

enter image description here 上面是时间线组件的显示方式,灰色背景面板和紫色背景面板(带有男人图标)都是子组件。 当您单击一个紫色面板时,vue-router会路由到详细信息页面,这时所有组件都被销毁(也就是说,当上述问题发生时)

2 个答案:

答案 0 :(得分:4)

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

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

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

Vue 2 反应性的简要说明

(不需要的可以跳过)

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

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

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

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

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

问题

所有依赖于同一个反应性事物的组件在同一个 Dep.subs 上插入一个 Watcher。在以下示例中,同一个 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 计算属性中做这样的事情,你的组件依赖于所有响应式事物链:stateaccountprofile

function myComputedProperty() {
    this.$store.state.account.profile.name;
}

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

$data

案例:路由器链接

issue #3500

案例:Vue I18n

这具有相同的潜在问题,但解释略有不同。请参阅issue #926

答案 1 :(得分:1)

这不是一个严重的问题,请参见您的mixins / options。
例如。每200个组件中的i18n(我的痛苦)将显示相同的结果。它删除了beforeDestroy上的许多观察者。如果没有i18n,列表的运行速度将提高30倍。
如何解决?将慢速挂钩处理程序移至父组件,并从其获取所需的数据/方法。

带有i18n

的示例
Vue.mixin({
    beforeCreate() {
        if (this.$options.useParentLocalization) {
            this._i18n = parent.$i18n;
        }
    },
});

用法:

new Vue({
  // i18n, <-- before
  useParentLocalization: true,
  components: {
    Component1
  }
})