在項目中,自定義了一個組件,在點擊子組件時,觸發選中事件,並經過$emit
,將子組件的數據傳遞給父組件,另外有一個全選按鈕來觸發子組件的所有選中。原本按照設想,子組件的事情交給子組件處理,在修改子組件的狀態時經過$emit
來進行數據交互,可是在數據量超出必定程度時,$emit
很影響性能。html
其實這就是在組件開發中,如何處理onSelect
以及selectionChange
。性能優化
下面幾段代碼是父子組件的簡單定義,在子組件中經過監聽data.isSelect
的變化,來觸發選中事件,設想是在使用組件的過程當中,依舊能夠經過設置data.isSelect
字段,來更新組件狀態,以及觸發選中。dom
父組件主要是監聽子組件的事件,組裝selection
,而且將子組件的事件繼續向外傳遞。性能
父組件template測試
<template>
<div>
<button v-model="checkedAll" @click="onSelectAll">全選</button>
<children v-for="item of children" @onSelect="onSelect" :data="item" ></children>
</div>
</template>
複製代碼
父組件script大數據
export default {
data() {
return {
checkedAll: false,
children: [],
selection: [],
};
},
methods: {
onSelectAll() {
this.children.forEach((child) => {
child.isSelect = true;
});
},
onSelect(child, selectStatus) {
if (selectStatus) {
this.selection.push(child);
} else {
this.selection = this.selection.filter(item => item != child);
}
this.$emit('onSelect', child, selectStatus);
this.$emit('selectionChange', this.selection);
},
},
}
複製代碼
子組件負責數據的展現,以及選中事件的處理,在 demo 中父組件只有一個,可是在實際項目中,有兩個不一樣類型的父組件使用,也是將共同邏輯抽出的一個良好的例子。優化
子組件templatethis
<template>
<div @click="onSelect">
<span>{{ data.isSelect ? 'checked' : 'unchecked' }}</span>
<span>{{ data.name }}</span>
</div>
</template>
複製代碼
子組件scriptspa
export default {
props: ['data'],
methods: {
onSelect() {
this.data.isSelect = !this.data.isSelect;
},
},
watch: {
'data.isSelect': function() {
this.$emit('onSelect', this.data, this.data.isSelect);
},
},
}
複製代碼
這個設計原本沒有問題,可是在全選時,若是數據較多,如 2000+
,全交給watch
來處理$emit
事件,每次$emit
只有幾百ms
,可是整個加起來一共會耗時幾分鐘。設計
沒有解決辦法時,選擇參考優秀的開源項目,在el-table
的源碼當中,toggleRowSelection
的源碼以下,經過控制參數emitChange
來控制事件的觸發與否,既能保證相同邏輯的複用,也能控制$emit
所帶來的性能影響。雖然不知道 Element-UI 本意是否是控制性能,可是在實際測試中,對於大數據量的表格,選中事件的響應效果仍是很理想的,因此借鑑這個思路,在組件代碼中增長控制事件相關的代碼。
toggleRowSelection(row, selected, emitChange = true) {
const changed = toggleRowStatus(this.states.selection, row, selected);
if (changed) {
const newSelection = (this.states.selection || []).slice();
// 調用 API 修改選中值,不觸發 select 事件
if (emitChange) {
this.table.$emit('select', newSelection, row);
}
this.table.$emit('selection-change', newSelection);
}
},
複製代碼
因爲父組件並非調用子組件的 API 修改子組件的狀態,選擇在子組件props
的data
中增長字段來控制,修改後的代碼以下:
父組件 onSelectAll
onSelectAll() {
this.children.forEach((child) => {
child.updateByComponent = true;
child.isSelect = true;
this.$nextTick(() => {
child.updateByComponent = false;
});
});
}
複製代碼
子組件 watch
watch: {
'data.isSelect': function() {
if (this.data.updateByComponent) return;
this.$emit('onSelect', this.data, this.data.isSelect);
},
}
複製代碼
修改後的代碼,將原來的響應時間由4分多鐘,下降到了400毫秒,帶來的實際體驗仍是很好的。在一樣的數據量狀況下,選中效果基本上是及時生效。
這是項目中的一次性能優化的反思,也是一次開源項目源碼的閱讀過程,雖然組件是我本身編寫的,坑是本身挖的,可是此次經歷也是本身在組件封裝上的一個成長。若是你有什麼好的建議,歡迎在評論中提出。
在編寫了最小 demo 之後,全選的響應在秒級,可是在項目中,更新isSelect
只涉及單個dom的更新,不知道爲何會產生這樣的影響。要了解透的話,得花時間一點一點的去嘗試了。