Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue - 優化大量數據同時渲染 #13

Open
JeffKko opened this issue Dec 16, 2020 · 0 comments
Open

Vue - 優化大量數據同時渲染 #13

JeffKko opened this issue Dec 16, 2020 · 0 comments

Comments

@JeffKko
Copy link
Owner

JeffKko commented Dec 16, 2020

前言

最近在專案上遇到小小的效能瓶頸

有一個畫面是顯示提貨點資訊的 list,
在初次渲染和使用filter時, 體感上會有明顯的 lag
原因是瀏覽器一次改變300多個dom節點造成的delay

Kapture 2020-12-16 at 16 21 28

來看看程式碼
這個 list 是由 state.currentRedemptionList 用 v-for 的方式渲染, 讓每一張 BsCard 呈現提貨點資料
而使用者可以藉由畫面上的 filter 對 state.currentRedemptionList 的數量進行增減

image

在程式碼加上 timelog

onBeforeUpdate(() => {
  console.time('update list')
})

onUpdated(() => {
  console.timeEnd('update list')
})

可以看到使用 特快取件的 filter 反選時,
因為 list 數量一下從 8 => 346
re-render 過程大約需耗時高達 300ms

image

第一步

試試看用 Object.freeze 凍結傳進來的 list, 除去 vue observe data 的效能消耗

<BsRedemptionPicker
    v-if="state.isShowRedemptionListPanel && stateSelectRedemption.sourceList"
    mode="declaration"
    :current-id="state.form.address"
    :redemption-list="Object.freeze(stateSelectRedemption.sourceList)"
    :express-name-key="address.addressFormat.expressNameKey"
    :redemption-shortcuts="address.addressFormat.consolidationRedemptionTypesGroupedShortcuts"
    @select="onSelectPanel"
    @goback="hideRedemptionListPanel"
  />

渲染耗時可以減少到大約 280ms, 效果不大

第二步

試試看將 state.currentRedemptionList 用 slice time 的方式重組
v-for 改成使用 stateTimeSlicing.list 去渲染
使用 setTimeout 在每次 eventLoop 時 分段將資料塞入列表

const stateTimeSlicing: any = reactive({
  list: [],
  perPage: 20,
  timer: null,
})
    
watch(
  () => state.currentRedemptionList,
  value => {
    timeSlice()
  },
  { immediate: true },
)

function timeSlice() {
  stateTimeSlicing.list = []
  clearTimeout(stateTimeSlicing.timer)
  stateTimeSlicing.timer = null

  loop(state.currentRedemptionList.length, 0)
}

function loop(curTotal: number, curIndex: number) {
  if (curTotal <= 0) {
    return false
  }
  const pageCount = Math.min(curTotal, stateTimeSlicing.perPage)

  stateTimeSlicing.timer = setTimeout(() => {
    stateTimeSlicing.list.push(
      ...state.currentRedemptionList.slice(
        curIndex,
        curIndex + pageCount,
      ),
    )
    loop(curTotal - pageCount, curIndex + pageCount)
  }, 0)

  return true
}

結果:在體感上已經感覺不到延遲了!

Kapture 2020-12-16 at 16 40 06

可以看到渲染時間被切成碎片化, 甚至第一次 component update 只需要3ms, 效果非常明顯

image

第三步

在優化後可以發現一個重點
雖然渲染速度變快了
但是卻造成畫面閃爍
原因是 在 timeSlice() 中的 stateTimeSlicing.list = []
這段程式碼會造成 list 為空的狀態, 並且觸發 vue re-render

我們稍微改一下程式碼就可以解決這個問題

function timeSlice() {
  clearTimeout(stateTimeSlicing.timer)
  stateTimeSlicing.timer = null

  loop(state.currentRedemptionList.length, 0)
}

function loop(curTotal: number, curIndex: number) {
  if (curTotal <= 0) {
    return false
  }
  const pageCount = Math.min(curTotal, stateTimeSlicing.perPage)

  stateTimeSlicing.timer = setTimeout(() => {
    if (curIndex === 0) stateTimeSlicing.list = []

    stateTimeSlicing.list.push(
      ...state.currentRedemptionList.slice(
        curIndex,
        curIndex + pageCount,
      ),
    )
    loop(curTotal - pageCount, curIndex + pageCount)
  }, 0)

  return true
}

看看結果, 已經防止了畫面跳動的問題

Kapture 2020-12-16 at 17 09 32

以上
大功告成!

參考資料

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant