Efficient Rendering of Large Datasets in Vue.js Frontend Applications

This article demonstrates several techniques—including server‑side data simulation, batch rendering with timers, requestAnimationFrame, pagination components, infinite scroll, and virtual lists—to efficiently display and interact with 100,000 records in a Vue.js front‑end without causing UI freezes or performance degradation.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Efficient Rendering of Large Datasets in Vue.js Frontend Applications

Simulating 100,000 Records

Use express to create an API endpoint that returns an array of 100,000 objects, each containing id, name, and value fields.

<span style="line-height: 26px">route.get("/bigData", (req, res) => {</span>
<span style="line-height: 26px">  res.header('Access-Control-Allow-Origin', '*'); // allow CORS</span>
<span style="line-height: 26px">  let arr = [] // store 100k items</span>
<span style="line-height: 26px">  for (let i = 0; i < 100000; i++) {</span>
<span style="line-height: 26px">    arr.push({</span>
<span style="line-height: 26px">      id: i + 1,</span>
<span style="line-height: 26px">      name: '名字' + (i + 1),</span>
<span style="line-height: 26px">      value: i + 1,</span>
<span style="line-height: 26px">    })</span>
<span style="line-height: 26px">  }</span>
<span style="line-height: 26px">  res.send({ code: 0, msg: '成功', data: arr }) // return data</span>
<span style="line-height: 26px">})</span>

Send Request on Button Click

Bind a click handler to an el-button that triggers an asynchronous request and stores the result in the component state.

<span style="line-height: 26px"><el-button :loading="loading" @click="onClick">点击请求加载</el-button></span>
<span style="line-height: 26px"><el-table :data="arr"></span>
<span style="line-height: 26px">  <el-table-column type="index" label="序" /></span>
<span style="line-height: 26px">  <el-table-column prop="id" label="ID" /></span>
<span style="line-height: 26px">  <el-table-column prop="name" label="名字" /></span>
<span style="line-height: 26px">  <el-table-column prop="value" label="对应值" /></span>
<span style="line-height: 26px"></el-table>

<span style="line-height: 26px">data() {</span>
<span style="line-height: 26px">  return {</span>
<span style="line-height: 26px">    arr: [],</span>
<span style="line-height: 26px">    loading: false,</span>
<span style="line-height: 26px">  };</span>
<span style="line-height: 26px">},

<span style="line-height: 26px">async onClick() {</span>
<span style="line-height: 26px">  // send request, assign data to arr</span>
<span style="line-height: 26px">}</span>

Solution 1: Direct Rendering (Not Recommended)

Fetching all data and assigning it directly to the table causes the page to freeze because the browser must render 100,000 DOM nodes at once.

<span style="line-height: 26px">async onClick() {</span>
<span style="line-height: 26px">  this.loading = true;</span>
<span style="line-height: 26px">  const res = await axios.get("http://ashuai.work:10000/bigData");</span>
<span style="line-height: 26px">  this.arr = res.data.data;</span>
<span style="line-height: 26px">  this.loading = false;</span>
<span style="line-height: 26px">}</span>

Solution 2: Batch Rendering with setTimeout

Split the 100k array into small chunks (e.g., 10 items per chunk) and render each chunk sequentially using setTimeout, which prevents the UI from freezing.

<span style="line-height: 26px">function averageFn(arr) {</span>
<span style="line-height: 26px">  let i = 0;</span>
<span style="line-height: 26px">  let result = [];</span>
<span style="line-height: 26px">  while (i < arr.length) {</span>
<span style="line-height: 26px">    result.push(arr.slice(i, i + 10));</span>
<span style="line-height: 26px">    i = i + 10;</span>
<span style="line-height: 26px">  }</span>
<span style="line-height: 26px">  return result;</span>
<span style="line-height: 26px">}</span>

<span style="line-height: 26px">async onClick() {</span>
<span style="line-height: 26px">  this.loading = true;</span>
<span style="line-height: 26px">  const res = await axios.get("http://ashuai.work:10000/bigData");</span>
<span style="line-height: 26px">  this.loading = false;</span>
<span style="line-height: 26px">  let twoDArr = averageFn(res.data.data);</span>
<span style="line-height: 26px">  for (let i = 0; i < twoDArr.length; i++) {</span>
<span style="line-height: 26px">    setTimeout(() => {</span>
<span style="line-height: 26px">      this.arr = [...this.arr, ...twoDArr[i]];</span>
<span style="line-height: 26px">    }, 1000 * i); // interval ~16.6 ms (1000/60)</span>
<span style="line-height: 26px">  }</span>
<span style="line-height: 26px">},</span>

Solution 3: Rendering with requestAnimationFrame

Use requestAnimationFrame instead of setTimeout to align rendering with the browser’s repaint cycle, achieving smoother updates.

<span style="line-height: 26px">async onClick() {</span>
<span style="line-height: 26px">  this.loading = true;</span>
<span style="line-height: 26px">  const res = await axios.get("http://ashuai.work:10000/bigData");</span>
<span style="line-height: 26px">  this.loading = false;</span>
<span style="line-height: 26px">  let twoDArr = averageFn(res.data.data);</span>
<span style="line-height: 26px">  const use2DArrItem = (page) => {</span>
<span style="line-height: 26px">    if (page > twoDArr.length - 1) return;</span>
<span style="line-height: 26px">    requestAnimationFrame(() => {</span>
<span style="line-height: 26px">      this.arr = [...this.arr, ...twoDArr[page]];</span>
<span style="line-height: 26px">      use2DArrItem(page + 1);</span>
<span style="line-height: 26px">    });</span>
<span style="line-height: 26px">  };</span>
<span style="line-height: 26px">  use2DArrItem(0);</span>
<span style="line-height: 26px">},</span>

Solution 4: Front‑End Pagination with Element‑UI

Leverage the built‑in pagination component of Element‑UI to display a subset of rows per page.

<span style="line-height: 26px"><template></span>
<span style="line-height: 26px">  <div class="box"></span>
<span style="line-height: 26px">    <el-table height="240" :data="showTableData" border style="width: 100%"></span>
<span style="line-height: 26px">      <el-table-column prop="name" label="姓名" width="180"></el-table-column></span>
<span style="line-height: 26px">      <el-table-column prop="age" label="年龄" width="180"></el-table-column></span>
<span style="line-height: 26px">      <el-table-column prop="home" label="家乡"></el-table-column></span>
<span style="line-height: 26px">    </el-table></span>
<span style="line-height: 26px">    <el-pagination
<span style="line-height: 26px">      layout="total, sizes, prev, pager, next, jumper"
<span style="line-height: 26px">      @size-change="handleSizeChange"
<span style="line-height: 26px">      @current-change="handleCurrentChange"
<span style="line-height: 26px">      :current-page="pageIndex"
<span style="line-height: 26px">      :page-size="pageSize"
<span style="line-height: 26px">      :page-sizes="[2, 4, 6, 10]"
<span style="line-height: 26px">      :total="total"
<span style="line-height: 26px">    ></el-pagination>
<span style="line-height: 26px">  </div>
<span style="line-height: 26px"></template>
<span style="line-height: 26px"><script>
<span style="line-height: 26px">export default {
<span style="line-height: 26px">  data() { return { pageIndex: 1, pageSize: 4, total: 0, allTableData: [], showTableData: [] }; },
<span style="line-height: 26px">  mounted() { /* fetch all data and init pagination */ },
<span style="line-height: 26px">  methods: {
<span style="line-height: 26px">    getShowTableData() { let begin = (this.pageIndex - 1) * this.pageSize; let end = this.pageIndex * this.pageSize; this.showTableData = this.allTableData.slice(begin, end); },
<span style="line-height: 26px">    handleCurrentChange(val) { this.pageIndex = val; this.getShowTableData(); },
<span style="line-height: 26px">    handleSizeChange(val) { this.pageIndex = 1; this.pageSize = val; this.getShowTableData(); }
<span style="line-height: 26px">  }
<span style="line-height: 26px">};
<span style="line-height: 26px"></script></span>

Solution 5: Scroll‑Bottom Auto‑Load

Detect when the scroll reaches the bottom using either scrollTop + clientHeight >= innerHeight or a MutationObserver, then load the next batch of data.

<span style="font-size: 14px; padding: 2px 4px; background-color: rgba(27,31,35,0.05); font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace; color: rgb(255,93,108);">scrollTop + clientHeight >= innerHeight</span>
<span style="font-size: 14px; padding: 2px 4px; background-color: rgba(27,31,35,0.05); font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace; color: rgb(255,93,108);">new MutationObserver()</span>

Solution 6: Virtual List (Windowing)

Render only the rows that are visible in the viewport and recycle DOM nodes as the user scrolls, dramatically reducing the number of elements the browser must manage.

<span style="line-height: 26px"><template></span>
<span style="line-height: 26px">  <div class="virtualListWrap" ref="virtualListWrap" @scroll="handleScroll" :style="{ height: itemHeight * count + 'px' }"></span>
<span style="line-height: 26px">    <div class="placeholderDom" :style="{ height: allListData.length * itemHeight + 'px' }"></div>
<span style="line-height: 26px">    <div class="contentList" :style="{ top: topVal }">
<span style="line-height: 26px">      <div v-for="(item, index) in showListData" :key="index" class="itemClass" :style="{ height: itemHeight + 'px' }">{{ item.name }}</div>
<span style="line-height: 26px">    </div>
<span style="line-height: 26px">    <div class="loadingBox" v-show="loading"><i class="el-icon-loading"></i> loading...</div>
<span style="line-height: 26px">  </div>
<span style="line-height: 26px"></template>
<span style="line-height: 26px"><script>
<span style="line-height: 26px">export default {
<span style="line-height: 26px">  data() { return { allListData: [], itemHeight: 40, count: 10, start: 0, end: 10, topVal: '0px', loading: false }; },
<span style="line-height: 26px">  computed: { showListData() { return this.allListData.slice(this.start, this.end); } },
<span style="line-height: 26px">  async created() { this.loading = true; const res = await axios.get("http://ashuai.work:10000/bigData"); this.allListData = res.data.data; this.loading = false; },
<span style="line-height: 26px">  methods: { handleScroll() { const scrollTop = this.$refs.virtualListWrap.scrollTop; this.start = Math.floor(scrollTop / this.itemHeight); this.end = this.start + this.count; this.topVal = scrollTop + 'px'; } }
<span style="line-height: 26px">};
<span style="line-height: 26px"></script></span>

Solution 7: VXETable Virtual Scrolling

For table‑based data, the vxe-table component provides built‑in virtual scrolling that can handle up to 50,000 columns and 300,000 rows.

<span style="line-height: 26px">// main.js</span>
<span style="line-height: 26px">import VXETable from 'vxe-table'</span>
<span style="line-height: 26px">import 'vxe-table/lib/style.css'</span>
<span style="line-height: 26px">Vue.use(VXETable)</span>

<span style="line-height: 26px"><template></span>
<span style="line-height: 26px">  <vxe-table border show-overflow ref="xTable1" height="300" :row-config="{ isHover: true }" :loading="loading"></span>
<span style="line-height: 26px">    <vxe-column type="seq"/></span>
<span style="line-height: 26px">    <vxe-column field="id" title="ID"/></span>
<span style="line-height: 26px">    <vxe-column field="name" title="名字"/></span>
<span style="line-height: 26px">    <vxe-column field="value" title="对应值"/></span>
<span style="line-height: 26px">  </vxe-table>
<span style="line-height: 26px"></template>
<span style="line-height: 26px"><script>
<span style="line-height: 26px">export default {
<span style="line-height: 26px">  data() { return { loading: false }; },
<span style="line-height: 26px">  async created() { this.loading = true; const res = await axios.get("http://ashuai.work:10000/bigData"); this.loading = false; this.$refs.xTable1.loadData(res.data.data); }
<span style="line-height: 26px">};
<span style="line-height: 26px"></script></span>

All the above methods aim to keep the UI responsive while dealing with massive front‑end data sets, allowing developers to choose the strategy that best fits their project requirements.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendperformanceinfinite scrollLarge Datavirtual-list
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.