1、場景描述
在項(xiàng)目開發(fā)中,遇到在表格中一次性加載完的需求,且加載數(shù)量不少,有幾百幾千條,并且每條都可能有自己的下拉框,輸入框來做編輯功能,此時(shí)普通的el-table肯定會(huì)導(dǎo)致瀏覽器卡死,那么怎么辦呢?
2、解決方案
當(dāng)然很多童鞋肯定會(huì)想到利用插件,其實(shí)我本人是不咋喜歡插件的,能自己寫就自己寫,畢竟插件可能也有bug或者啥的,萬一出現(xiàn)了,作者不去改咋辦,所以我總結(jié)了下面幾個(gè)解決方法
提示:本篇博客基本都用的tsx + vue3 composition-api體驗(yàn)版 來寫的噢,用vue2或者vue3模板語法寫會(huì)更簡單噢,照葫蘆畫瓢邏輯都是一樣的,我這里就不寫了,想了解相關(guān)的vue3知識(shí)我另一篇博客上有噢~
Vue3知識(shí)點(diǎn)學(xué)習(xí)
1、滾動(dòng)觸底分頁(良)
當(dāng)縱向滾動(dòng)條觸底的時(shí)候,加載新的數(shù)據(jù)到當(dāng)前表格中,邏輯如下:
table.scrollTop + table.clientHeight === table.scrollHeight
當(dāng)上述成立時(shí)候觸發(fā)加載,table為表格dom, 但是如果數(shù)據(jù)很多的話,每次滾動(dòng)都會(huì)將新的tr加入到表格中,那表格tr dom總數(shù)還是會(huì)依次遞增,dom一多照樣卡死
2、滾動(dòng)區(qū)間分頁(良+)
如果你沒有表格編輯功能,全是展示數(shù)據(jù)的話,那么這個(gè)解決方案已經(jīng)完全可以了
比如你當(dāng)前表格可顯示區(qū)域能夠展示十條數(shù)據(jù),那么首次進(jìn)來時(shí),顯示數(shù)據(jù)的區(qū)間為 [0, 9],每次表格滾動(dòng)時(shí),都動(dòng)態(tài)的去獲取當(dāng)前展示的區(qū)間,其實(shí)這種方式是比較好的,為啥呢?因?yàn)椴还苣?span style="color:#fe2c24;">幾千幾萬條數(shù)據(jù),我同一時(shí)刻就只有10行,完全不會(huì)因?yàn)閠r數(shù)量過多導(dǎo)致瀏覽器渲染卡頓,當(dāng)然這可視區(qū)域能展示多少條數(shù)據(jù),是有你表格可視高度和單行tr高度一起決定的
const selectWrap = table.querySelector('.el-table__body-wrapper');
const selectRow = table.querySelector('table tr');
展示tr數(shù)量 = Math.ceil(selectWrap.clientHeight / selectRow.clientHeight)
tsx:頁面
setup() {
? return () => (
? ? <el-table
? ? ? data={visibleResult.value} // 可視區(qū)域的數(shù)據(jù)
? ? >
? ? </el-table>
? )
}
ts:邏輯
/** 表格上展示的數(shù)據(jù) */
const visibleResult = computed(() => {
return result.value.filter((_item, index) => {
if (index < curStartIndex.value) {
return false;
} else if (index > curEndIndex.value) {
return false;
} else {
return true;
}
});
})
那么如何去控制這個(gè)滾動(dòng)區(qū)間呢?這個(gè)方法很多,監(jiān)聽滾動(dòng)條的scroll,當(dāng)向下滾動(dòng)或向上滾動(dòng),我們都可以監(jiān)聽到,然后改變curStartIndex和curEndIndex的值就可以改變啦,這樣的話,光是只看數(shù)據(jù)倒是解決了,但是要是表格要實(shí)現(xiàn)編輯效果咋辦?每行數(shù)據(jù)有十幾個(gè)下拉框和輸入框,你要知道,el-select dom層級很高,像這種el-select數(shù)量一多,就算你當(dāng)前展示區(qū)域沒有多少條數(shù)據(jù),也會(huì)導(dǎo)致渲染卡頓的,所以就有下面的優(yōu)化版方案
3、滾動(dòng)區(qū)間分頁 + 表格編輯(優(yōu))
當(dāng)前表格可視區(qū)域有30條數(shù)據(jù),每條數(shù)據(jù)有10個(gè)el-select和5個(gè)el-input(當(dāng)然可能有童鞋會(huì)說,el-select dom層級高,那我就自己寫select鴨~但是你自己寫的未必有el-select好看且功能相當(dāng))。此時(shí)首次加載就很卡頓了,原因就是el-select多了,那么我首先想的是這些el-select又不是直接要用,為啥不在我點(diǎn)擊這個(gè)單元格時(shí)候再彈出下拉呢?不點(diǎn)擊單元格時(shí)候,就直接展示文本效果(el-select沒有值時(shí)展示 '請選擇', 有值時(shí)展示你選擇的值)
例圖如下:
?滾動(dòng)區(qū)間分頁
?不管你有多少條數(shù)據(jù),我始終首次只加載這么十多個(gè)tr,tr數(shù)量不變,變化的是每次的區(qū)間取值,大家會(huì)發(fā)現(xiàn)下面有個(gè)tr id為 virtual-scroll,那這是干什么 的呢?其實(shí)核心思路是:
顯示區(qū)的高度 + 已經(jīng)滾動(dòng)過的高度 + 虛擬滾動(dòng)條高度 === 總的數(shù)據(jù)高度
table-wrapper.clientHeight + table-wrapper.scrollTop + virtual-scroll.height === data.lentth * tr.clientHeight
?是不是發(fā)現(xiàn)和方案1類似,但是區(qū)別不同的是,虛擬條高度可是一來就會(huì)被計(jì)算出來的,因?yàn)殚_始時(shí)候scrollTop為0, 那么 虛擬條高度 就是 總的數(shù)據(jù)高度 - 顯示區(qū)高度
tsx: 通過自定義指令來監(jiān)聽表格的滾動(dòng),返回值觸發(fā)loadMore方法,來決定顯示區(qū)展示數(shù)據(jù)的區(qū)間
setup() {
return () => (
<el-table
data={visibleResult.value} // 可視區(qū)域的數(shù)據(jù)
{...{ directives: [{ name: 'load-more', value: methods.loadMore }] }}
>
</el-table>
)
}
ts: 邏輯
/** 表格上展示的數(shù)據(jù) */
const visibleResult = computed(() => {
return result.value.filter((_item, index) => {
if (index < curStartIndex.value) {
return false;
} else if (index > curEndIndex.value) {
return false;
} else {
return true;
}
});
})
const methods = {
/**
* 懶加載回調(diào)
* @param startIndex 區(qū)段位置開始索引
* @param endIndex 區(qū)段位置結(jié)束索引
*/
loadMore(startIndex: number, endIndex: number) {
curStartIndex.value = startIndex
curEndIndex.value = endIndex
},
}
那上面是實(shí)現(xiàn)滾動(dòng)區(qū)間的代碼,那說的表格編輯相關(guān)在哪里呢?下面即是:
表格編輯
當(dāng)我們表格中有很多下拉框和輸入框時(shí),其實(shí)拖垮性能的多半是el-select,那解決方法是將渲染input和select的邏輯提出來,input不做處理,select當(dāng)前單元格row,column索引和focusCell的值一致時(shí)說明單元格被聚焦了,就顯示el-select,否則直接展示文本
關(guān)鍵方法及屬性講解:
inputChange:輸入框回調(diào)
selectChange:下拉框選擇回調(diào)
selectPerofrmance:下拉框當(dāng)前值渲染
domPropsInnerHTML: 等同于v-html,但是jsx中不能用v-html
decorateHeader:表格各列數(shù)據(jù)我都是動(dòng)態(tài)配置的,后續(xù)我會(huì)出一期博客來講解vue3+tsx下如何封裝表格
然后效果大致如下,總結(jié)下就是同一時(shí)間最多只會(huì)存在一個(gè)el-select,既然el-select dom減少了,那么表格渲染速度就自然而然快了
?上面例圖中被黃色圈中的就是聚焦,沒有被聚焦的都展示文本,源碼講解如下:
const focusCell = ref<string>('0,0') // 表格數(shù)據(jù)第0行第0列
?你在el-table 里有 cell-click 這個(gè)事件,它會(huì)將當(dāng)前row, column全都回調(diào)回去,那么你就會(huì)知道你當(dāng)前點(diǎn)擊單元格的索引值, 我們將索引值生成, 在同一時(shí)間,focusCell只會(huì)有一條數(shù)據(jù),所以我們直接用字符串來存儲(chǔ)就好
<el-table
? on-cell-click={methods.cellClick}
>
</el-table>
const methods = {
? cellClick(row, column) {
? ? if (focusCell.value !== `${row.index},${column.index}`) {
? ? ? ? focusCell.value = `${row.index},${column.index}`
? ? ? }
? }
}
那么關(guān)鍵來了,下拉框單元格聚焦的時(shí)候,我們才顯示下拉框,其他時(shí)候展示文本,輸入框類型單元格聚焦的時(shí)候不做處理,代碼該如何寫呢?
setup() {
return () => {
/** 輸入框類型渲染 */
const inputDomRender = (scope, item) => (
<el-input
value={scope.row[item.prop]}
on-input={e => methods.inputChange(e, scope, item)}
/>
)
/** 下拉框類型渲染 */
const selectDomRender = (scope, item) => (
(focusCell.value === `${scope.row.autoIndex},${scope.column.index}` ? <el-select
value-key='id'
value={scope.row[item.prop]}
onChange={e => methods.selectChange(e, scope, item)}
>
{
item.selects.map(item1 => {
return <el-option
key={item1.id}
label={item1.label}
value={item1.id}
>
</el-option>
})
}
</el-select> : <div
domPropsInnerHTML={methods.selectPerofrmance(scope.row[item.prop], item.prop)}></div>)
)
return <el-table
on-cell-click={methods.cellClick}
>
{
decorateHeader.map((item: TableLabel) => {
return <el-table-column
width={item.width}
label={item.label}
align={item.align ? item.align : 'center'}
prop={item.prop}
scopedSlots={{
default: scope => {
return <div
>
{
item.mode !== 'input' ? selectDomRender(scope, item) : inputDomRender(scope, item)
}
</div>
}
}}
>
</el-table-column>
})
}
</el-table>
}
}
自此上述兩步優(yōu)化,其實(shí)就能使我們一次性加載幾千條可編輯數(shù)據(jù)不會(huì)卡頓了~
3、v-load-more自定義指令
源碼:
import {
VNodeDirective
} from 'vue'
let timeout;
/** 設(shè)置表格滾動(dòng)區(qū)間 */
const setRowScrollArea = (topNum, showRowNum, binding) => {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
binding.value.call(null, topNum, topNum + showRowNum);
});
};
const loadMore= {
bind(el: Element, _binding) {
setTimeout(() => {
// 創(chuàng)建虛擬滾動(dòng)條
const selectWrap = el.querySelector('.el-table__body-wrapper');
const selectTbody = selectWrap.querySelector('table tbody');
const createElementTR = document.createElement('tr');
createElementTR.id = 'virtual-scroll'
selectTbody.append(createElementTR); // 先行將虛擬滾動(dòng)條加入進(jìn)來
})
},
componentUpdated(el: Element, binding: VNodeDirective, vnode, oldVnode) {
setTimeout(() => {
const dataSize = vnode.data.attrs['data-size'];
const oldDataSize = oldVnode.data.attrs['data-size'];
// 當(dāng)數(shù)量相同時(shí),表明當(dāng)前未發(fā)生更新,減少后續(xù)操作
if (dataSize === oldDataSize) {
return;
}
const selectWrap = el.querySelector('.el-table__body-wrapper');
const selectTbody = selectWrap.querySelector('table tbody');
const selectRow = selectWrap.querySelector('table tr');
// 當(dāng)一行都沒有,說明無數(shù)據(jù)渲染,但一般邏輯都不會(huì)進(jìn)入這里
if (!selectRow) {
return;
}
const rowHeight = selectRow.clientHeight;
// 能夠在當(dāng)前顯示區(qū)的展示條數(shù),本項(xiàng)目就是11條
const showRowNum = Math.round(selectWrap.clientHeight / rowHeight);
const createElementTRHeight = (dataSize - showRowNum) * rowHeight;
const createElementTR = selectTbody.querySelector('#virtual-scroll')
// 監(jiān)聽滾動(dòng)后事件
selectWrap.addEventListener('scroll', function() {
let topPx = this.scrollTop;
let topNum = Math.round(topPx / rowHeight);
const minTopNum = dataSize - showRowNum;
if (topNum > minTopNum) {
topNum = minTopNum;
}
if (topNum < 0) {
topNum = 0;
topPx = 0;
}
selectTbody.setAttribute('style', `transform: translateY(${topPx}px)`);
// 本來觸底的話,應(yīng)該設(shè)置為0,但是觸底后 就沒有滾動(dòng)條了
createElementTR.setAttribute('style', `height: ${createElementTRHeight - topPx > 0 ? createElementTRHeight - topPx : rowHeight}px;`);
setRowScrollArea(topNum, showRowNum, binding);
})
});
}
}
export default loadMore
?不太了解自定義指令是啥的可以參考我另一篇博客
vue學(xué)習(xí)(6)自定義指令詳解及常見自定義指令
4、有趣拓展
1、我想在上述表格中對指定列實(shí)現(xiàn)高亮搜索怎么做?
當(dāng)有值時(shí)滾動(dòng)到指定位置,無值時(shí)不動(dòng),那首先在加載數(shù)據(jù)時(shí),要先寫下面代碼
// 先讓他觸發(fā)滾動(dòng),才能讓virtual-scroll高度生成
table.$el.querySelector('.el-table__body-wrapper').scrollTo({ top: 1, behavior: 'smooth' })
輸入框執(zhí)行邏輯如下,防抖肯定是要的,然后搜索的列是?originName,當(dāng)發(fā)現(xiàn)有搜索到值時(shí),找到第一個(gè)被匹配到的行索引,然后去計(jì)算表格應(yīng)該滾動(dòng)到哪個(gè)高度位置,然后滾動(dòng)
/**
* 滾動(dòng)定位到表格指定位置
* @param flag 是否能執(zhí)行滾動(dòng)的標(biāo)志
* @returns
*/
scrollToTable: () => $debounce(function() {
// 當(dāng)沒有值時(shí),不進(jìn)行搜索
if (dialogSearchKey.value) {
const vmEl = table.value.$el;
const selectWrap = vmEl.querySelector('.el-table__body-wrapper')
if (vmEl) {
const autoIndex = result.value.find((item: TableDataItem) => {
return item.originName.indexOf(dialogSearchKey.value) !== -1
})?.autoIndex ?? -1
if (autoIndex !== -1) {
scrollToIndex(selectWrap, autoIndex)
}
}
}
}, 500, false),
/**
* 表格滾動(dòng)到指定索引值行
* @param selectWrap 表格dom
* @param autoIndex 索引
*/
scrollToIndex(selectWrap, autoIndex) {
const showNum = 12 // 當(dāng)顯示條數(shù)小于次數(shù)時(shí),不進(jìn)行滾動(dòng)操作
const topPx = autoIndex * columnHeight.value
if (autoIndex > showNum) {
selectWrap.scrollTo({ top: topPx, behavior: 'smooth' })
}
}
?然后高亮被搜索文字源碼如下:
setup() {
return () => {
return <el-table
on-cell-click={methods.cellClick}
>
{
decorateHeader.map((item: TableLabel) => {
return <el-table-column
width={item.width}
label={item.label}
align={item.align ? item.align : 'center'}
prop={item.prop}
scopedSlots={{
default: scope => {
return <div
>
<div
class='multiline'
domPropsInnerHTML={methods.textRender(scope.row[item.prop], item.prop)}
/>
</div>
}
}}
>
</el-table-column>
})
}
</el-table>
}
}
?props.heightLight ?為輸入框的搜索關(guān)鍵詞文章來源:http://www.zghlxwxcb.cn/news/detail-449688.html
/**
* 文字渲染
* @param word 被渲染的文字
* @param prop 屬性名
* @returns 替換后的渲染文字
*/
textRender(word: string, prop: string): string {
const reg = new RegExp(`${props.heightLight}`, 'ig')
// 有搜索關(guān)鍵詞 && 是否有該子字符串
if (props.heightLight && word.indexOf(props.heightLight) !== -1) {
return word.replace(reg, `<font color='red'>$&</font>`)
// 正常返回的列
} else {
return word
}
}
---本篇還是相當(dāng)實(shí)用,喜歡就一鍵三連吧~歡迎評論---文章來源地址http://www.zghlxwxcb.cn/news/detail-449688.html
到了這里,關(guān)于【Element-ui】el-table大數(shù)據(jù)量渲染卡頓問題的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!