記 Vue 大型表單項(xiàng)目的一個(gè)性能問題

問題場(chǎng)景

身為一個(gè)表單表格工程師础芍,自然日復(fù)一日的寫著表單表格,本以為已經(jīng)沒啥難點(diǎn)的時(shí)候轉(zhuǎn)眼間就來了一個(gè)有意思的情況贤惯,在超大量 數(shù)據(jù)綁定在 vue 的時(shí)候出現(xiàn)了表單操作起來卡頓的情況菜皂。

這里先貼上本項(xiàng)目出現(xiàn)的情況演示的 github 上的地址,tag1.0.1(https://github.com/everlose/more-form-demo/tree/v1.0.1

如圖所見镇草,當(dāng)在 input 輸入數(shù)據(jù)的時(shí)候眶痰,連續(xù)輸入會(huì)感覺明顯的延遲。

image

那么梯啤,這到底是怎么回事竖伯?

代碼

上述的表單數(shù)據(jù)項(xiàng)修改頻繁由后端返回,于是在前端需要渲染從后端返回的 68kb 的一個(gè) JSON 數(shù)據(jù)串,包括所有配置表單項(xiàng)以及其可能的選項(xiàng)值七婴,數(shù)據(jù)見這里

核心渲染是有這么一段

<div class="basic-info ct-form" v-for="(config, configIndex) in formConfig" :key="configIndex">

<h3 class="form__title">{{config.title}}</h3>

<el-form class="form-content" ref="form" label-width="150px">

    <el-form-item

        class="basic-form-item"

        v-for="(item, itemIndex) in config.formItems"

        :key="itemIndex"

        :prop="item.code"

        :label="item.name"

        :required="item.required"

        :rules="item.rules">

        <el-radio-group

            v-if="item.type === 'radio'"

            v-model="formData[item.code]">

            <el-radio

                v-for="(option, radioIndex) in formOptions[item.optionCode]"

                :key="option.value"

                :label="option.value"

                :disabled="item.disabled">

                {{ option.label }}

            </el-radio>

        </el-radio-group>

        <el-input

            v-else-if="item.type === 'input'"

            :class="{ longInput: item.isLongInput }"

            :placeholder="item.placeholder || '請(qǐng)輸入'"

            v-model="formData[item.code]"

            :label="item.label"

            :disabled="item.disabled"

            :maxlength="item.maxLength">

        </el-input>

        <el-select

            v-else-if="item.type === 'select'"

            v-model="formData[item.code]"

            :disabled="item.disabled"

            :placeholder="item.placeholder || '請(qǐng)選擇'">

            <el-option

                v-for="(option, optionsIndex) in formOptions[item.optionCode]"

                :key="option.value"

                :label="option.label"

                :value="option.value">

            </el-option>

        </el-select>

    </el-form-item>

</el-form>

</div>

這就是一個(gè)簡(jiǎn)單的雙層遍歷渲染所有表單配置項(xiàng)的模版代碼祟偷,其中的 formConfig 正是所有配置表單項(xiàng),數(shù)據(jù)量極多打厘。formOptions 掛載了所有表單選項(xiàng)值修肠,也是動(dòng)輒幾千項(xiàng)。

思路

正當(dāng)我對(duì)著這么高的操作延時(shí)發(fā)愁的時(shí)候婚惫,組里一個(gè)大佬提醒我氛赐,可能是 Vue.prototype._update 這個(gè)觸發(fā)的太頻繁了。

我急忙找到這一段打了個(gè)斷點(diǎn)調(diào)試

image

Vue.prototype._update 這函數(shù)里觸發(fā)的是 VNode 虛擬節(jié)點(diǎn)的比對(duì)更新先舷,打斷點(diǎn)調(diào)試后發(fā)現(xiàn)實(shí)際上這是一個(gè)循環(huán)艰管,在控制臺(tái)里輸出 this.$el 的時(shí)候能得到正在深度遍歷中的節(jié)點(diǎn),沿著根結(jié)點(diǎn) App(也是 formConfig 數(shù)據(jù)綁定的作用域) 開始直到具體觸發(fā)輸入的那個(gè)表單元素蒋川。

在本項(xiàng)目里是使用了遍歷輸出所有的表單元素牲芋,并且當(dāng)前組件的作用域是直接掛在根結(jié)點(diǎn)上的,是否就是這個(gè)遍歷引發(fā)了如此高的延時(shí)呢捺球?于是我找到上圖右側(cè)的調(diào)用堆棧缸浦,發(fā)現(xiàn)正是 flushSchedulerQueue 函數(shù)寫著一個(gè) for 循環(huán)。

image

在 flushSchedulerQueue 函數(shù)中的 for 循環(huán)里頭尾插入代碼來獲取耗費(fèi)時(shí)間氮兵。

結(jié)果得知輸入時(shí)的延遲大概在 300ms 之上裂逐。

image

似乎問題就找到了,flushSchedulerQueue 函數(shù)針對(duì) data 中數(shù)據(jù)的修改把 watcher 推送進(jìn)隊(duì)列里在更新泣栈,這一循環(huán)耗費(fèi)的時(shí)間比較長(zhǎng)卜高。

解決

其實(shí)早在調(diào)試 Vue.prototype._update 函數(shù)就初見端倪,循環(huán)中的 this.$el 從當(dāng)前組件的根部開始深度遍歷南片,遍歷了太多次掺涛,那么只要想辦法縮小當(dāng)前組件所綁定的數(shù)據(jù)量就解決了。

于是核心代碼調(diào)整為

<div class="basic-info ct-form" v-for="(config, configIndex) in formConfig" :key="configIndex">

<edit-form :config="config" :data="formData" :options="formOptions"></edit-form></div>

只是用一個(gè) edit-form 包裹剛剛所有的 el-form-item 的渲染代碼就解決了疼进,再次調(diào)試 Vue.prototype._update 得出遍歷節(jié)點(diǎn) this.$el 已經(jīng)變?yōu)橄聢D所示的 div.edit-form 了薪缆,flushSchedulerQueue 函數(shù) for 循環(huán)的延遲也變?yōu)?10ms 左右

image

修復(fù)版的代碼在2.0.0的tag上,這里貼上鏈接(https://github.com/everlose/more-form-demo/tree/v2.0.0

后記

本質(zhì)上這就是一個(gè)原則伞广,最好不要在一個(gè)vue組件上直接綁定如此多的數(shù)據(jù)拣帽,如果有大量數(shù)據(jù)請(qǐng)分多個(gè)組件綁定。這么淺嘗輒止實(shí)在讓人不夠盡興嚼锄,于是這里貼上 Vue.prototype._update 前的關(guān)鍵部分調(diào)用堆棧以及其函數(shù)作用诞外。

找到項(xiàng)目中 node_modules 下的 vue.esm.js

往input里輸入將會(huì)觸發(fā)model data的更新

978 set: function reactiveSetter (newVal)

訂閱器dep是數(shù)據(jù)綁定和視圖更新的關(guān)鍵,這里觸發(fā)去通知相關(guān)視圖的更新

994 dep.notify();

673 Dep.prototype.notify

notify函數(shù)里的subs實(shí)際上是Watcher對(duì)象的實(shí)例灾票,這里觸發(fā)視圖更新操作

677 subs[i].update(); subs實(shí)際上是包裹watcher的數(shù)組

3093 Watcher.prototype.update

把watcher塞進(jìn)一個(gè)隊(duì)列里,這里是和異步更新視圖有關(guān)茫虽。

3100 queueWatcher(this);

2945 function queueWatcher (watcher) push到隊(duì)列里

nextTick是具體做異步更新的部分

2963 nextTick(flushSchedulerQueue);

1778 function nextTick (cb, ctx)

異步操作實(shí)際上是原生 H5 MessageChannel API 通道通信來推送消息來實(shí)現(xiàn)變化刊苍。

1738 port.postMessage(1);

注意在異步操作中既们,最終傳入的回調(diào)函數(shù)被執(zhí)行來進(jìn)行下面視圖的更新。這里是執(zhí)行一個(gè)任務(wù)調(diào)度隊(duì)列的調(diào)度過程正什,需要循環(huán)遍歷啥纸。

2856 function flushSchedulerQueue

3108 Watcher.prototype.run

Evaluate the getter, and re-collect dependencies.

3043 Watcher.prototype.get

watcher中的getter的name就叫updateComponent,于是被執(zhí)行

2689 updateComponent

2690 vm._update(vm._render(), hydrating);

進(jìn)入vue的生命周期中的update函數(shù)

2548 Vue.prototype._update

patch做的是vnode的節(jié)點(diǎn)比對(duì)婴氮,最終把新的vnode結(jié)構(gòu)渲染到具體視圖斯棒,不再多做描述。

2572 vm.$el = vm.patch(prevVnode, vnode);

貼上提供思路的大佬的github地址: https://github.com/answershuto

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末主经,一起剝皮案震驚了整個(gè)濱河市荣暮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌罩驻,老刑警劉巖穗酥,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異惠遏,居然都是意外死亡砾跃,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門节吮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抽高,“玉大人,你說我怎么就攤上這事透绩∏搪睿” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵渺贤,是天一觀的道長(zhǎng)雏胃。 經(jīng)常有香客問我,道長(zhǎng)志鞍,這世上最難降的妖魔是什么瞭亮? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮固棚,結(jié)果婚禮上统翩,老公的妹妹穿的比我還像新娘。我一直安慰自己此洲,他們只是感情好厂汗,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著呜师,像睡著了一般娶桦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天衷畦,我揣著相機(jī)與錄音栗涂,去河邊找鬼。 笑死祈争,一個(gè)胖子當(dāng)著我的面吹牛斤程,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播菩混,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼忿墅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了沮峡?” 一聲冷哼從身側(cè)響起疚脐,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎帖烘,沒想到半個(gè)月后亮曹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秘症,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年照卦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乡摹。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡役耕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出聪廉,到底是詐尸還是另有隱情瞬痘,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布板熊,位于F島的核電站框全,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏干签。R本人自食惡果不足惜津辩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望容劳。 院中可真熱鬧喘沿,春花似錦、人聲如沸竭贩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽留量。三九已至窄赋,卻和暖如春哟冬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背寝凌。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工柒傻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人较木。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像青柄,于是被迫代替她去往敵國(guó)和親伐债。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 問題場(chǎng)景 身為一個(gè)表單表格工程師致开,自然日復(fù)一日的寫著表單表格峰锁,本以為已經(jīng)沒啥難點(diǎn)的時(shí)候轉(zhuǎn)眼間就來了一個(gè)有意思的情況...
    深海鯽魚堡閱讀 378評(píng)論 1 0
  • 問題場(chǎng)景 身為一個(gè)表單表格工程師,自然日復(fù)一日的寫著表單表格双戳,本以為已經(jīng)沒啥難點(diǎn)的時(shí)候轉(zhuǎn)眼間就來了一個(gè)有意思的情況...
    深海鯽魚堡閱讀 2,141評(píng)論 2 0
  • # 傳智播客vue 學(xué)習(xí)## 1. 什么是 Vue.js* Vue 開發(fā)手機(jī) APP 需要借助于 Weex* Vu...
    再見天才閱讀 3,525評(píng)論 0 6
  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內(nèi)容虹蒋,還有我對(duì)于 Vue 1.0 印象不深的內(nèi)容。關(guān)于...
    云之外閱讀 5,045評(píng)論 0 29
  • ## 框架和庫的區(qū)別?> 框架(framework):一套完整的軟件設(shè)計(jì)架構(gòu)和**解決方案**飒货。> > 庫(lib...
    Rui_bdad閱讀 2,890評(píng)論 1 4