如何处理大数据的Vue 2内存使用量(~50 000个对象)

时间:2018-01-30 07:22:56

标签: arrays performance memory vue.js vuejs2

我正在尝试为Vue 2上的大型半复杂对象集合实现一个表视图。基本上,我们的想法是从DB到JS缓存中收集5万到10万行之间的任何行,然后动态分析使用实时过滤器(文本搜索)构建表视图。表格中的每一行都是可切换的,这意味着单击该行可将行更改为编辑模式,从而为该特定字段/单元格启用类似Excel的编辑。

每个对象具有大约100-150个字段/属性,但在表中的任何给定时刻仅显示一定数量的'em'(表列可以实时切换)。对于大型数据集,似乎DB正在推动大约10-100mb的JSON数据,在这个用例中是可以接受的。 Renderwise性能不是问题 - 过滤器工作得足够快,只有有限的结果呈现给DOM。

一切都已经有效,过滤器,列出~100行反对过滤器(+“显示100多个” - 机制等),但是当我将大约8000个对象加载到数组中时,我达到了内存限制。这似乎保留了2 GB的RAM,这是在Chrome停止运行JS代码之后(尽管奇怪的是我没有收到任何警告/错误)。

我对行的内存使用情况进行了基准测试,似乎~1000行保留了大约300mb的内存。这很可能由Vue反应观察者保留。

三个问题:

  1. 有没有办法切换特定数组列表对象的反应性(通过索引等),这样数组本身内的对象是不可观察/不可变的,除非特别调用变为可变(即当用户点击行时,启用编辑模式)?
  2. 你如何为Vue实现大型数据集的处理,因为反应似乎是内存使用的瓶颈? 请不要建议“限制后端内的结果”,因为它不是我在这里寻求的解决方案(即使我可能需要创建两部分过滤,一个用于获取较小的初始数据集和一个用于实时过滤)。基本上我试图将“记忆结束”的界限从8 000 - >通过Vue重新思考数据架构,获得80 000。数据集在Vue的数据变量中存储为反应性的唯一问题是什么?
  3. 我有一个想法是将“items”-dataset变为不可观察/不反应的Object.freeze或类似的方法,并有表格来渲染两个数据集:一个用于非反应,一个用于那些目前处于编辑模式(单击行时将被推送到“editableItems”数据集)...这里有任何建议(更简单,所以我能够处理一个数组中的所有内容吗?)
  4. 我在Angular 1上做了类似的应用程序,它处理了5万行,所以我确信它应该在Vue 2中可行...应该只是找到处理反应性的方法

3 个答案:

答案 0 :(得分:7)

编辑12.03.2019 - 本答案末尾的其他提示

自从我提出这个问题以来已经有一段时间了,我终于优化了我项目的这一部分。我想为那些有这些表现和/或记忆问题的人提供一些指示。

Vue文档从未真正解释过,但正如Andrey指出的那样,您可以将组件对象用作自定义对象的数据存储。对象名单。毕竟,它只是一个普通的javascript对象。

优化后,我的列表组件设置看起来像这样:

module.exports = {
    items: [],
    mixins: [sharedUtils],
    data: function() {
        return {
            columns: {
                all: []
    etc... Lot's of data & methods

items-array填充了数千个复杂对象(大约80mb数据,6mb压缩),我将其视为非反应性。事实证明这不是我想象的问题 - 我没有使用v-for直接针对项目我已经使用了结构,当用户点击某个过滤器按钮和/或输入的字符串时,我触发了该数组的过滤过滤(例如名称)。基本上这个" processFilters" -method通过非响应的items-array并返回filteredItems,它存储在数据上下文中。因此,它会在变异时自动变为反应性。

<tr v-for="item in filteredItems" 

这样,filteredItems中的所有项都会保持反应,但当它们被过滤掉时也会失去反应性,从而节省了大量内存。高达1200mb缩水至400mb,这正是我想要的。聪明!

有几个问题需要解决。由于项目不存在于数据上下文中,因此无法在模板中直接使用它。这意味着不是写作......

<div v-if="items.length > 0 && everythingElseIsReady">

...我必须存储items-array的长度来分隔数据prop。这也可以用计算值修复,但我喜欢保留这些属性。

放弃主数据阵列的反应性毕竟不是一件坏事 - 最重要的部分是要理解直接针对该基础阵列中的项目进行的修改永远不会触发任何更改到UI和/或子组件(douh)。只要您以一种隐藏数据容器的方式分离您的代码,这就不应该是一个问题。它包含后端的所有结果,并且您拥有该大容器的较小(已过滤)表示数组。通过使用良好的REST架构,您应该已经很好地使用非活动数据存储,只要您记得在非活动数据存储中保存项目后检查已更新到最新版本。

此外,令我感到困惑的是,在性能方面,有多少微型组件与数百行有关。渲染很明显,但即使我要传递数千次大型道具(因为我有数千个输入单元实例),它似乎没有打到内存。其中一种对象是我的全局翻译 - 键/值对对象,有超过20 000行翻译的字符串......但它仍然无关紧要。这是有道理的,因为Javascript使用对象引用并且Vue Core似乎被正确编码,因此只要您使用这样的配置对象作为道具,您只需将数千个对象引用到相同的数据集。

最后,我说开始对复杂的CRUD对象发疯,而不用担心内存限制!

非常感谢安德烈波波夫向正确的方向轻推!

提示(12.03.2019)

因为它已经有一段时间了,因为我继续使用大型&amp;复杂的数据集我决定放弃一些简短的想法&amp;提示。

  1. 考虑如何管理主记录(即人员或产品)与相关记录(子对象/关系对象)。尝试限制为子组件注入的数据量,因为您可能会为不同的主记录多次表示相同的子对象。问题是,这些对象实际上可能不是引用对象!
  2. 考虑具有person-object的情况,其中包含city-object。多个人居住在同一个城市,但是当您从后端获取JSON数据时,您确定那些重复的城市对象实际上是同一个城市(人与人之间共享/引用的城市对象),或类似对象的多个表示(与数据完全相同,但在引擎盖下每个都是一个单独的实例/唯一对象)。让我们说你有5万人,每人包含相同的子对象/财产&#34; city&#34;:{id:4,name:&#34; Megatown&#34;你刚刚获得了50 000个单独的城市实例,而不仅仅是一个?是person1.city === person2.city,还是它们看起来一样,仍然是两个不同的对象?

    如果您不确定是引用共享的城市对象还是使用了几十个类似子对象的实例,您可以在人员列表组件中进行引用。您的人员包含city-id,因此使用单独的REST方法(getCities)获取城市列表,并在UI级别进行配对。这样,您只有一个城市列表,您可以从该列表中解析城市并将其注入人员,从而仅参考一个城市。或者,您可以从列表中解析城市,并将其作为属性传递给您的人员组件。

    还要确保考虑子对象的目的是什么。你需要它是被动的,还是静态的?为了节省大量的内存,你可以告诉他们:#c; person.city = city&#34;,这将为每个人组件注入,但如果它需要被动,那么你需要使用Vue。 set -method ...并记住,如果每个城市都需要自己的实例(这样每个人都有类似的城市对象,但每个人都需要编辑属性)那么你需要确保你没有使用被引物体!因此,您很可能需要克隆城市对象,这将占用浏览器内存。

    1. 您的微组件可能包含只读状态和编辑器状态的单独视图状态。这很常见。实际上,您每次都在创建该微组件的实例,从而将该组件初始化数千次。
    2. 考虑一下您拥有带表格和表格行的类似Excel的电子表格的情况。每个单元格都包含您的自定义&#34; my-input&#34; -component,从您的布局中获取&#34; readonly&#34; -property。如果UI处于只读状态,那么您只显示my-input-component内的标签部分,但是否则您正在显示带有一些特殊条件的输入标记(例如,对日期时间,数字,文本, textarea,select-tag等)。现在让我们假设你有100行,有20列,所以你实际上是在初始化2000 my-input-components。现在问题是 - 什么可以改进(性能方面)?

      好吧,您可以将readonly-label与my-input-component分隔到列表视图,这样您就可以显示readonly-version(标签),或者显示可编辑的my-input-component。这样你就有了v-if条件,这确保了那些2000微元件不会被初始化,除非你特别要求初始化它(因为行或整个布局从readonly移动 - &gt; editable -state)...当Vue不需要创建2000个组件时,您可能会猜测浏览器对内存的影响有多大。

      如果您面临的页面加载速度非常慢,则可能根本不是VUE。查看呈现给HTML的HTML标签数量。当您拥有大量标签时,HTML执行得相当糟糕。证明这一点的最简单方法之一是重复使用2000个选项的select-tag 100次,或者使用单个20000选项select-tag。通过使用大量具有不必要的包装div等的微组件,你可能会溢出大量的html标签......你的标签越少,标签越少,浏览器和标签所需的渲染性能就越低。 CPU。

      尝试通过示例学习良好的HTML标记架构。例如,您可以研究Trello -services仪表板视图是如何编程的。这是一个非常简单和漂亮的代表,相当半复杂的服务,只有极少量的sub-div。

      有许多方法可以改善内存处理,但我要说最重要的方法是分离&#34;隐藏&#34;来自可见对象的对象,如我原来的答案所述。第二部分是理解差异或实例与引用对象。第三是限制对象之间不必要的数据传递量。

      就我个人而言,我还没有尝试过这个,但是存在一个Vue-virtual-scroller组件,它只是作为看似无限量数据的包装器来处理任何数量的数据。查看@ https://github.com/Akryum/vue-virtual-scroller这个概念,让我知道它是否为您解决了问题。

      我希望这些指南提供一些优化组件的建议。永不放弃希望,总有改进的余地!

答案 1 :(得分:3)

从我读过的所有内容中,我发现您只是不需要对该数据的反应性,因为:

  

表格中的每一行都是可切换的,这意味着单击该行可将行更改为编辑模式,从而为该特定字段/单元格启用类似Excel的编辑

这意味着行不可编辑,如果没有用户交互,数据就无法变异。

  

每个对象有大约100-150个字段/属性,但在表格中的任何给定时刻只显示一定数量的'em(表格列可以实时切换)。

你保持反应但不显示它们。

现在你的问题

  

是否有办法切换特定数组列表对象的反应性(通过索引等),以便数组本身内的对象不被观察/不可变,除非特别调用变为可变(即当用户单击行时,启用编辑模式?

如果有一个项目可以一次编辑,那么为什么要保持一切被动?您可以轻松使用单个变量来监听更改。

  

你如何为Vue实现大型数据集的处理,因为反应似乎会阻碍内存使用?

这一切都与实施有关 - 当你需要大量的项目列表被反应时,你很少会遇到这种情况。您拥有的物品越多,使用反应性所需的事件就越多。如果你有50k项目并且只有一些事件需要变异(比如用户手动修改数据),那么你可以轻松地监听那些事件并手动调整反应,而不是让Vue处理所有数据。您可以查看可以让您的生活更轻松的Vuex:)

  

我的一个想法是将“items”-dataset转换为不可观察/不反应的Object.freeze或类似的方法,并有表格来呈现两个数据集:一个用于非反应,一个用于那些目前处于编辑模式(单击行时将推送到“editableItems”数据集)

这是朝着正确的方向发展,但不需要支持两个阵列。想象一下使用这样的东西:

data: function() {
    return {
        editingItem: {}
        // when editing is enabled bind the input fields to this item
    }
},
created: function() {
    this.items = [] // your items, can be used in markdown in the loop, but won't be reactive!
},
watch: {
    editingItem: function(data) {
        // this method will be called whenever user edits the input fields
        // here you can do whatever you want
        // like get item's id, find it in the array and update it's properties
        // something like manual reactivity ;)
    }
}

答案 2 :(得分:1)

  • 我遇到了一个确切的问题,我需要显示一个巨大的列表,认为至少有50000个高度可变的物品,但我找不到任何解决方案
  • 一般的解决方案是构建/使用虚拟滚动。
  • 它仅在DOM中保留一些项目,而其余项目则在DOM中进行编辑。但是,它会根据您是否向上/向下滚动来不断更改可见的内容
  • 除非您对vue-virtual-scrollervue-virtual-scroll-list之类的高度进行硬编码,否则我发现的现有库不会处理动态高度。
  • vue-collection-cluster可让您动态计算高度,但严重滞后于50000个项目
  • 所以我想出了自己的解决方案,它可以在50000+个项目上滚动“超级平滑”,甚至可以测试10万个项目,而且效果很好
  • 实现动态行高的想法就像这样
  • 我们需要维护数组中每个项目的高度列表 enter image description here

  • 基于滚动“顶部”的位置,我们垂直应用了一个transformY变换,以抵消我们一直显示给用户的一些项目

enter image description here

  • 我在解决方案中添加了足够的注释,以便您轻松了解发生了什么情况

HTML

<script type="text/x-template" id="virtual-list">
   <div id="root" ref="root">
      <div id="viewport" ref="viewport" :style="viewportStyle">
        <div id="spacer" ref="spacer" :style="spacerStyle">
         <div v-for="i in visibleItems" :key="i.id" class="list-item" :ref="i.id" :data-index="i.index" @click="select(i.index)"  :class="i.index === selectedIndex ? 'selected': ''">
           <div>{{ i.index + ' ' + i.value }}</div>
   </div>
   </div>
   </div>
   </div>
</script>
<div id="app">
   <h1 class="title">
      Vue.js Virtual + Infinite Scroll + Dynamic Row Heights + Arrow Key Navigation + No Libraries
   </h1>
   <p class="subtitle">
      No hardcoding of heights necessary for each row. Set emitEnabled to false
      for max performance. Tested with <span id="large_num">50000</span> items...
   </p>
   <div id="list_detail">
      <div id="list">
         <virtual-list></virtual-list>
      </div>
      <div id="detail">
         <table>
            <tbody>
               <tr>
                  <th class="caption">Root Container Height</th>
                  <td>{{store['root-height']}} px</td>
               </tr>
               <tr>
                  <th class="caption">Viewport Height</th>
                  <td>{{store['viewport-height']}} px</td>
               </tr>
               <tr>
                  <th class="caption">Smallest Row Height</th>
                  <td>{{store['smallest-height']}} px</td>
               </tr>
               <tr>
                  <th class="caption">Largest Row Height</th>
                  <td>{{store['largest-height']}} px</td>
               </tr>
               <tr>
                  <th class="caption">Scroll Top</th>
                  <td>{{store['scroll-top']}} px</td>
               </tr>
               <tr>
                  <th class="caption">Page Index</th>
                  <td>{{store['page-start-index']}}</td>
               </tr>
               <tr>
                  <th class="caption">Start Index</th>
                  <td>{{store['start-index']}}</td>
               </tr>
               <tr>
                  <th class="caption">End Index</th>
                  <td>{{store['end-index']}}</td>
               </tr>
               <tr>
                  <th class="caption">Translate Y</th>
                  <td>{{store['translate-y']}} px</td>
               </tr>
            </tbody>
         </table>
         <p><b>Visible Item Indices on DOM</b> {{store['visible-items']}}</p>
         <p><b>Total Height Till Current Page</b> {{store['page-positions']}}</p>
         <p>
            <b>Row's Vertical Displacement From Viewport Top</b>
            {{store['row-positions']}}
         </p>
         <p><b>Heights</b> {{store['heights']}}</p>
      </div>
   </div>
</div>

CSS

@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}


/**
Apply Scroll Bar Styles

https://css-tricks.com/the-current-state-of-styling-scrollbars/
*/
html {
  --scrollbarBG: #181C25;
  --thumbBG: orange;
}
body::-webkit-scrollbar {
  width: 11px;
}
body {
  scrollbar-width: thin;
  scrollbar-color: var(--thumbBG) var(--scrollbarBG);
}
body::-webkit-scrollbar-track {
  background: var(--scrollbarBG);
}
body::-webkit-scrollbar-thumb {
  background-color: var(--thumbBG) ;
  border-radius: 6px;
  border: 3px solid var(--scrollbarBG);
}


html {
  height: 100%;
}

body {
  min-height: 100%;
  height: 100%;
  padding: 2rem;
  color: #AAA;
  background: #181C25;
  font-family: 'Open Sans', sans-serif;
  font-size: 0.9rem;
  line-height: 1.75;
}

#app {
  height: 100%;
  display: flex;
  flex-direction: column;
}

#list_detail {
  display: flex;
  height: 70%;
}

#list {
  flex: 2;
  height: 100%;
}

#detail {
  flex: 1;
  padding: 1rem;
  overflow: auto;
  height: 100%;
}

#root {
  height: 100%;
  overflow: auto;
}

.list-item {
  padding: 0.75rem 0.25rem;
  border-bottom: 1px solid rgba(255, 255, 0, 0.4);
}

.title {
  color: white;
  text-align: center;
}

.subtitle {
  color: orange;
  text-align: center;
}

table {
  width: 100%;
  table-layout: fixed;
  text-align: center;
}

th.caption {
  text-align: left;
  color: #00BEF4;
  font-weight: 100;
  padding: 0.5rem 0;
}

td {
  text-align: left;
}

b{
  font-weight: 100;
  color: #00BEF4;
}

#large_num {
  color: red;
}

.selected {
  background: midnightblue;
}

Vue.js

我在此处只能使用30000个字符,因此HERE is the complete code on CodePen

限制

  • 目前无法通过调整屏幕大小来很好地工作

功能

  • 50000多个项目轻松滚动
  • 支持箭头导航,就像本地列表一样

  • 如有任何疑问,请在评论中告诉我