摘要:虛擬列表的實(shí)現(xiàn)有多種方案,本文以組件為基礎(chǔ)進(jìn)行分析。常見的無限滾動(dòng)便是延遲渲染的一種實(shí)現(xiàn),而虛擬列表則是按需渲染的一種實(shí)現(xiàn)。接下來,本文會(huì)簡單介紹虛擬列表的一種實(shí)現(xiàn)方案。實(shí)現(xiàn)本章節(jié)將會(huì)創(chuàng)建一個(gè)組件,并結(jié)合代碼,慢慢梳理虛擬列表的實(shí)現(xiàn)。
在 列表數(shù)據(jù)的展示優(yōu)化 一文中,提到了對(duì)于列表形態(tài)的數(shù)據(jù)展示的按需渲染。這種方式是指根據(jù)容器元素的高度以及列表項(xiàng)元素的高度來顯示長列表數(shù)據(jù)中的某一個(gè)部分,而不是去完整地渲染長列表,以提高無限滾動(dòng)的性能。而按需顯示方案的實(shí)現(xiàn)就是本文標(biāo)題中說的虛擬列表。
虛擬列表的實(shí)現(xiàn)有多種方案,本文以 react-virtual-list 組件為基礎(chǔ)進(jìn)行分析。原文鏈接:https://github.com/dwqs/blog/...什么是虛擬列表?
在正文之前,先對(duì)虛擬列表做個(gè)簡單的定義。
根據(jù)上文,虛擬列表是按需顯示思路的一種實(shí)現(xiàn),即虛擬列表是一種根據(jù)滾動(dòng)容器元素的可視區(qū)域來渲染長列表數(shù)據(jù)中某一個(gè)部分?jǐn)?shù)據(jù)的技術(shù)。
簡而言之,虛擬列表指的就是「可視區(qū)域渲染」的列表。有三個(gè)概念需要了解一下:
滾動(dòng)容器元素:一般情況下,滾動(dòng)容器元素是 window 對(duì)象。然而,我們可以通過布局的方式,在某個(gè)頁面中任意指定一個(gè)或者多個(gè)滾動(dòng)容器元素。只要某個(gè)元素能在內(nèi)部產(chǎn)生橫向或者縱向的滾動(dòng),那這個(gè)元素就是滾動(dòng)容器元素考慮每個(gè)列表項(xiàng)只是渲染一些純文本。在本文中,只討論元素的縱向滾動(dòng)。
可滾動(dòng)區(qū)域:滾動(dòng)容器元素的內(nèi)部內(nèi)容區(qū)域。假設(shè)有 100 條數(shù)據(jù),每個(gè)列表項(xiàng)的高度是 50,那么可滾動(dòng)的區(qū)域的高度就是 100 * 50。可滾動(dòng)區(qū)域當(dāng)前的具體高度值一般可以通過(滾動(dòng)容器)元素的 scrollHeight 屬性獲取。用戶可以通過滾動(dòng)來改變列表在可視區(qū)域的顯示部分。
可視區(qū)域:滾動(dòng)容器元素的視覺可見區(qū)域。如果容器元素是 window 對(duì)象,可視區(qū)域就是瀏覽器的視口大小(即視覺視口);如果容器元素是某個(gè) div 元素,其高度是 300,右側(cè)有縱向滾動(dòng)條可以滾動(dòng),那么視覺可見的區(qū)域就是可視區(qū)域。
實(shí)現(xiàn)虛擬列表就是在處理用戶滾動(dòng)時(shí),要改變列表在可視區(qū)域的渲染部分,其具體步驟如下:
計(jì)算當(dāng)前可見區(qū)域起始數(shù)據(jù)的 startIndex
計(jì)算當(dāng)前可見區(qū)域結(jié)束數(shù)據(jù)的 endIndex
計(jì)算當(dāng)前可見區(qū)域的數(shù)據(jù),并渲染到頁面中
計(jì)算 startIndex 對(duì)應(yīng)的數(shù)據(jù)在整個(gè)列表中的偏移位置 startOffset,并設(shè)置到列表上
計(jì)算 endIndex 對(duì)應(yīng)的數(shù)據(jù)相對(duì)于可滾動(dòng)區(qū)域最底部的偏移位置 endOffset,并設(shè)置到列表上
建議參考下圖理解一下上面的步驟:
元素 L 代指當(dāng)前列表中的最后一個(gè)元素
從上圖可以看出,startOffset 和 endOffset 會(huì)撐開容器元素的內(nèi)容高度,讓其可持續(xù)的滾動(dòng);此外,還能保持滾動(dòng)條處于一個(gè)正確的位置。
為什么需要虛擬列表?虛擬列表是對(duì)長列表的一種優(yōu)化方案。在前端開發(fā)中,會(huì)碰到一些不能使用分頁方式來加載列表數(shù)據(jù)的業(yè)務(wù)形態(tài),我們稱這種列表叫做長列表。比如,在一些外匯交易系統(tǒng)中,前端會(huì)準(zhǔn)實(shí)時(shí)的展示用戶的持倉情況(收益、虧損、手?jǐn)?shù)等),此時(shí)對(duì)于用戶的持倉列表一般是不能分頁的。
在本篇文章中,我們把長列表定義成數(shù)據(jù)長度大于 999,并且不能使用分頁的形式來展示的列表。
如果對(duì)長列表不作優(yōu)化,完整地渲染一個(gè)長列表,到底需要多長時(shí)間呢?接下來會(huì)寫一個(gè)簡單的 demo 來測(cè)試以下。
本文 demo 的測(cè)試環(huán)境:Macbook Pro(Core i7 2.2G, 16G), Chrome 69,React 16.4.1
在 demo 中,我們先測(cè)一下瀏覽器渲染 10000 個(gè)簡單的節(jié)點(diǎn)需要多長時(shí)間:
import React from "react" const count = 10000 function createMarkup (doms) { return doms.length ? { __html: doms.join(" ") } : { __html: "" } } export default class DOM extends React.Component { constructor (props) { super(props) this.state = { simpleDOMs: [] } this.onCreateSimpleDOMs = this.onCreateSimpleDOMs.bind(this) } onCreateSimpleDOMs () { const array = [] for (var i = 0; i < count; i++) { array.push("" + i + "") } this.setState({ simpleDOMs: array }) } render () { return () } }Creat large of DOMs:
當(dāng)點(diǎn)擊 Button 時(shí),會(huì)調(diào)用 onCreateSimpleDOMs 創(chuàng)建 10000 個(gè)簡單節(jié)點(diǎn)。從 Chrome 的 Performance 標(biāo)簽頁看到的數(shù)據(jù)如下:
從上圖可以看到,從 Event Click 到 Paint,總共用了大約 693ms,渲染時(shí)的主要時(shí)間消耗情況如下:
Recalculate Style:40.80ms
Layout:518.55ms
Update Layer Tree:11.84ms
在 Recalculate Style 和 Layout 階段,ReactDOM 調(diào)用了 setInnerHTML 方法,其內(nèi)部主要通過 innerHTML 方法,將創(chuàng)建好的 html 片段添加到對(duì)應(yīng)節(jié)點(diǎn)
然后,我們創(chuàng)建 10000 個(gè)稍微復(fù)雜點(diǎn)的節(jié)點(diǎn)。修改組件如下:
import React from "react" function createMarkup (doms) { return doms.length ? { __html: doms.join(" ") } : { __html: "" } } export default class DOM extends React.Component { constructor (props) { super(props) this.state = { complexDOMs: [] } this.onCreateComplexDOMs = this.onCreateComplexDOMs.bind(this) } onCreateComplexDOMs () { const array = [] for (var i = 0; i < 5000; i++) { array.push(``) } this.setState({ complexDOMs: array }) } render () { return (#${i} eligendi voluptatem quisquam
Modi autem fugiat maiores. Doloremque est sed quis qui nobis. Accusamus dolorem aspernatur sed rem.
) } }Creat large of DOMs:
當(dāng)點(diǎn)擊 Button 時(shí),會(huì)調(diào)用 onCreateComplexDOMs。從 Chrome 的 Performance 標(biāo)簽頁看到的數(shù)據(jù)如下:
從上圖可以看到,從 Event Click 到 Paint,總共用了大約 964.2ms,渲染時(shí)的主要時(shí)間消耗情況如下:
Recalculate Style:117.07ms
Layout:538.00ms
Update Layer Tree:31.15ms
對(duì)于上述測(cè)試各進(jìn)行 5 次,然后取各指標(biāo)的平均值,統(tǒng)計(jì)結(jié)果如下:
- | Recalculate Style | Layout | Update Layer Tree | Total |
---|---|---|---|---|
渲染簡單節(jié)點(diǎn) | 199.66ms | 523.72ms | 12.572ms | 735.952ms |
渲染復(fù)雜節(jié)點(diǎn) | 114.684ms | 806.05ms | 31.328ms | 952.512ms |
Total = Recalculate Style + Layout + Update Layer Tree
demo 的測(cè)試代碼:test code
從上面的測(cè)試結(jié)果中可以看到,渲染 10000 個(gè)節(jié)點(diǎn)就需要 700ms+,實(shí)際業(yè)務(wù)中的列表每個(gè)節(jié)點(diǎn)都需要 20 個(gè)左右的節(jié)點(diǎn),布局也會(huì)復(fù)雜很多,在 Recalculate Style 和 Layout 階段也會(huì)耗費(fèi)更長的時(shí)間。那么,700ms 也僅能渲染 300 ~ 500 個(gè)左右的列表項(xiàng),所以完整的長列表渲染基本上很難達(dá)到業(yè)務(wù)上的要求的。而非完整的長列表渲染一般有兩種方式:按需渲染和延遲渲染(即懶渲染)。常見的無限滾動(dòng)便是延遲渲染的一種實(shí)現(xiàn),而虛擬列表則是按需渲染的一種實(shí)現(xiàn)。
延遲渲染不在本文討論范圍。接下來,本文會(huì)簡單介紹虛擬列表的一種實(shí)現(xiàn)方案。
實(shí)現(xiàn)本章節(jié)將會(huì)創(chuàng)建一個(gè) VirtualizedList 組件,并結(jié)合代碼,慢慢梳理虛擬列表的實(shí)現(xiàn)。
為了簡化,我們?cè)O(shè)定 window 為滾動(dòng)容器元素,給 html 和 body 元素均添加樣式規(guī)則 height: 100%,設(shè)定可視區(qū)域?yàn)闉g覽器的窗口大小。VirtualizedList 在 DOM 元素的布局上將參考Twitter 的移動(dòng)端:
class VirtualizedList extends Component { constructor (props) { super(props) this.state = { startOffset: 0, endOffset: 0, visibleData: [] } this.data = new Array(1000).fill(true) this.startIndex = 0 this.endIndex = 0 this.scrollTop = 0 } render () { const {startOffset, endOffset} = this.state return () } }{ // render list }
在虛擬列表上的實(shí)現(xiàn)上,也分為兩種情形:列表項(xiàng)是固定高度的和列表項(xiàng)是動(dòng)態(tài)高度的。
列表項(xiàng)是固定高度的既然列表項(xiàng)是固定高度的,那約定沒個(gè)列表項(xiàng)的高度為 60,列表數(shù)據(jù)的長度為 1000。
首先,我們根據(jù)可視區(qū)域的高度估算可視區(qū)域能渲染的元素個(gè)數(shù):
const height = 60 const bufferSize = 5 // ... this.visibleCount = Math.ceil(window.clientHeight / height)
然后,計(jì)算 startIndex 和 endIndex,并先初始化初次需要渲染的數(shù)據(jù):
// ... updateVisibleData (scrollTop) { const visibleData = this.data.slice(this.startIndex, this.endIndex) const endOffset = (this.data.length - this.endIndex) * height this.setState({ startOffset: 0, endOffset, visibleData }) } componentDidMount () { // 計(jì)算可渲染的元素個(gè)數(shù) this.visibleCount = Math.ceil(window.innerHeight / height) + bufferSize this.endIndex = this.startIndex + this.visibleCount this.updateVisibleData() }
如上文所說,endOffset 是計(jì)算 endIndex 對(duì)應(yīng)的數(shù)據(jù)相對(duì)于可滾動(dòng)區(qū)域底部的偏移位置。在本 demo 中,可滾動(dòng)區(qū)域的高度就是 1000 60,因而 endIndex 對(duì)應(yīng)的數(shù)據(jù)相距底部的偏移就是 (1000 - endIndex) 60。
由于是初始化初次需要渲染的數(shù)據(jù),因而 startOffset 的初始值是 0。
根據(jù)上述代碼,可以得知,要計(jì)算可見區(qū)域需要渲染的數(shù)據(jù),只要計(jì)算出 startIndex 就行,因?yàn)?visibleCount 是一個(gè)定值,bufferSize 是一個(gè)緩沖值,用來增加一定的緩存區(qū)域,讓正常滑動(dòng)速度的時(shí)候不會(huì)顯得那么突兀。而 endIndex 的值就等于 startIndex 加上 visibleCount;同時(shí),當(dāng)用戶滾動(dòng)改變可見區(qū)域的數(shù)據(jù)時(shí),還需要計(jì)算 startOffset 的值,以保證新的數(shù)據(jù)會(huì)出現(xiàn)在用戶瀏覽器的視口中:
如果不計(jì)算 startOffset 的值,那本應(yīng)該渲染在可視區(qū)域內(nèi)的元素會(huì)渲染到可視區(qū)域之外。從上圖可以看到,startOffset 的值就是元素8的上邊框 (可視區(qū)域內(nèi)最上面一個(gè)元素) 到元素1的上邊框的偏移量。元素8稱為 錨點(diǎn)元素,即可視區(qū)域內(nèi)的第一個(gè)元素。 因而,我們需要定義一個(gè)變量來緩存錨點(diǎn)元素的一些位置信息,同時(shí)也要緩存已渲染的元素的位置信息:
// ... // 緩存已渲染元素的位置信息 this.cache = [] // 緩存錨點(diǎn)元素的位置信息 this.anchorItem = { index: 0, // 錨點(diǎn)元素的索引值 top: 0, // 錨點(diǎn)元素的頂部距離第一個(gè)元素的頂部的偏移量(即 startOffset) bottom: 0 // 錨點(diǎn)元素的底部距離第一個(gè)元素的頂部的偏移量 } // ... cachePosition (node, index) { const rect = node.getBoundingClientRect() const top = rect.top + window.pageYOffset this.cache.push({ index, top, bottom: top + height }) } // ...
方法 cachePosition 會(huì)在每個(gè)列表項(xiàng)組件渲染完后(componentDidMount)進(jìn)行調(diào)用,node 是對(duì)應(yīng)的列表項(xiàng)節(jié)點(diǎn)元素,index 是節(jié)點(diǎn)的索引值:
// Item.jsx // ... componentDidMount () { this.props.cachePosition(this.node, this.props.index) } render () { /* eslint-disable-next-line */ const {index} = this.props return ({ this.node = node }}>) } // ...#${index} eligendi voluptatem quisquam
Modi autem fugiat maiores. Doloremque est sed quis qui nobis. Accusamus dolorem aspernatur sed rem.
緩存了錨點(diǎn)元素和已渲染元素的位置信息之后,接下來就可以處理用戶的滾動(dòng)行為了。以用戶向下滾動(dòng)(scrollTop 值增大的方向)為例:
// ... // 計(jì)算 startIndex 和 endIndex updateBoundaryIndex (scrollTop) { scrollTop = scrollTop || 0 //用戶正常滾動(dòng)下,根據(jù) scrollTop 找到新的錨點(diǎn)元素位置 const anchorItem = this.cache.find(item => item.bottom >= scrollTop) this.anchorItem = { ...anchorItem } this.startIndex = this.anchorItem.index this.endIndex = this.startIndex + this.visibleCount } // 滾動(dòng)事件處理函數(shù) handleScroll (e) { if (!this.doc) { // 兼容 iOS Safari/Webview this.doc = window.document.body.scrollTop ? window.document.body : window.document.documentElement } const scrollTop = this.doc.scrollTop if (scrollTop > this.scrollTop) { if (scrollTop > this.anchorItem.bottom) { this.updateBoundaryIndex(scrollTop) this.updateVisibleData() } } else if (scrollTop < this.scrollTop) { // 向上滾動(dòng)(`scrollTop` 值減小的方向) } this.scrollTop = scrollTop } // ...
在滾動(dòng)事件處理函數(shù)中,會(huì)去更新 startIndex、endIndex 以及新的錨點(diǎn)元素的位置信息(即更新 startOffset),然后就可以動(dòng)態(tài)的去更新可視區(qū)域的渲染數(shù)據(jù)了:
完整的代碼在可以戳:固定高度的虛擬列表實(shí)現(xiàn)列表項(xiàng)是動(dòng)態(tài)高度的
這種情形下,實(shí)現(xiàn)的思路和列表項(xiàng)固高大同小異。而小異之處就在于緩存列表項(xiàng)的位置信息時(shí),怎么拿到列表項(xiàng)的精確高度?首先要更改 cachePosition 的部分邏輯:
// ... cachePosition (node, index) { const rect = node.getBoundingClientRect() const top = rect.top + window.pageYOffset this.cache.push({ index, top, bottom: top + rect.height // 將 height 更為 rect.height }) } // ...
由于列表項(xiàng)的高度不固定,那要怎么計(jì)算 visibleCount 呢?我們先考慮每個(gè)列表項(xiàng)只是渲染一些純文本。在實(shí)際項(xiàng)目中,有的列表項(xiàng)可能只有一行文本,有的列表項(xiàng)可能有多行文本,此時(shí),我們要基于項(xiàng)目的實(shí)際情況,給列表項(xiàng)一個(gè)預(yù)估的高度:estimatedItemHeight。
比如,有一個(gè)長列表要渲染用戶的文章摘要,并規(guī)定摘要顯示不超過三行,那么我們?nèi)×斜淼那?10 個(gè)列表項(xiàng)的高度平均值作為預(yù)估高度。當(dāng)然,為了預(yù)估高度更精確,我們是可以擴(kuò)大取樣樣本的。
既然有了預(yù)估高度,那么將原先代碼中的 height 替換成 estimatedItemHeight,就可以計(jì)算出 visibleCount 了:
// ... const estimatedItemHeight = 80 // ... // 計(jì)算可渲染的元素個(gè)數(shù) this.visibleCount = Math.ceil(window.innerHeight / estimatedItemHeight) + bufferSize // ...
我們通過 faker.js 來創(chuàng)建一些隨機(jī)數(shù)據(jù),并賦值給 data:
// ... function fakerData () { const a = [] for (let i = 0; i < 1000; i++) { a.push({ id: i, words: faker.lorem.words(), paragraphs: faker.lorem.sentences() }) } return a } // ... this.data = fakerData() // ...
修改一下列表項(xiàng)的 render 邏輯,其它不變:
// Item.jsx // ... render () { /* eslint-disable-next-line */ const {index, item} = this.props return ({ this.node = node }}>) } // ...#${index} {item.words}
{item.paragraphs}
此時(shí),列表項(xiàng)的高度已經(jīng)是動(dòng)態(tài)的了,根據(jù)渲染的實(shí)際情況,我們給的預(yù)估高度是 80:
完整的代碼在可以戳:動(dòng)態(tài)高度的虛擬列表實(shí)現(xiàn)
那如果列表項(xiàng)渲染的不是純文本呢?比如渲染的是圖文,那在 Item 組件的 componentDidMount 去調(diào)用 cachePosition 方法時(shí),能拿到對(duì)應(yīng)節(jié)點(diǎn)的正確高度嗎?在渲染圖文的情況下,因?yàn)閳D片會(huì)發(fā)起網(wǎng)絡(luò)請(qǐng)求,此時(shí)并不能保證在列表項(xiàng)組件掛載(執(zhí)行 componentDidMount)的時(shí)候圖片渲染好了,那此時(shí)對(duì)應(yīng)節(jié)點(diǎn)的高度就是不準(zhǔn)確的,因而在用戶滾動(dòng)改變可見區(qū)域渲染的數(shù)據(jù)時(shí),就可能出現(xiàn)元素相互重疊的情況:
在這種情況下,如果我們能監(jiān)聽 Item 組件節(jié)點(diǎn)的大小變化就能獲取其正確的高度了。ResizeObserver 或許就可以滿足我們的需求,其提供了監(jiān)聽 DOM 元素大小變化的能力,但在撰寫本文時(shí),僅 Chrome 67 及以上版本支持,其它主流瀏覽器均為提供支持。以下是我搜集的一些資料,供你參考(自備梯子):
ResizeObserver: It’s Like document.onresize for Elements
ResizeObserver
caniuse#resizeobserver
總結(jié)在本文中,首先對(duì)虛擬列表進(jìn)行了簡單的定義,然后從長列表的角度分析了為什么需要虛擬列表,最后就列表項(xiàng)固高和不固高兩個(gè)場(chǎng)景下以一個(gè)簡單的 demo 詳細(xì)講述了虛擬列表的實(shí)現(xiàn)思路。
在列表項(xiàng)是動(dòng)態(tài)高度的場(chǎng)景下,分析了渲染純文本和圖文混合的場(chǎng)景。前者給出了一個(gè)具體的 demo,針對(duì)后者對(duì)于怎么監(jiān)聽元素大小的變化提供了參考的 ResizeObserver 方案?;?ResizeObserver 的方案呢,我也實(shí)現(xiàn)了一個(gè)支持渲染圖文混合(當(dāng)然也支持純文本)的虛擬列表組件 react-virtual-list,供你參考。
當(dāng)然,這并不是唯一一種實(shí)現(xiàn)虛擬列表的方案。在組件 react-virtual-list 的實(shí)現(xiàn)過程中,也閱讀了不同虛擬列表組件的源碼,如: react-tiny-virtual-list、react-window、react-virtualized 等,后續(xù)的系列文章我會(huì)從源碼的角度逐一分析。
原文:https://github.com/dwqs/blog/...
參考Complexities of an Infinite Scroller
Infinite List and React
聊聊前端開發(fā)中的長列表
再談前端虛擬列表的實(shí)現(xiàn)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/98491.html
摘要:應(yīng)用常見安全漏洞一覽注入注入就是通過給應(yīng)用接口傳入一些特殊字符,達(dá)到欺騙服務(wù)器執(zhí)行惡意的命令。此外,適當(dāng)?shù)臋?quán)限控制不曝露必要的安全信息和日志也有助于預(yù)防注入漏洞。 web 應(yīng)用常見安全漏洞一覽 1. SQL 注入 SQL 注入就是通過給 web 應(yīng)用接口傳入一些特殊字符,達(dá)到欺騙服務(wù)器執(zhí)行惡意的 SQL 命令。 SQL 注入漏洞屬于后端的范疇,但前端也可做體驗(yàn)上的優(yōu)化。 原因 當(dāng)使用外...
摘要:應(yīng)用常見安全漏洞一覽注入注入就是通過給應(yīng)用接口傳入一些特殊字符,達(dá)到欺騙服務(wù)器執(zhí)行惡意的命令。此外,適當(dāng)?shù)臋?quán)限控制不曝露必要的安全信息和日志也有助于預(yù)防注入漏洞。 web 應(yīng)用常見安全漏洞一覽 1. SQL 注入 SQL 注入就是通過給 web 應(yīng)用接口傳入一些特殊字符,達(dá)到欺騙服務(wù)器執(zhí)行惡意的 SQL 命令。 SQL 注入漏洞屬于后端的范疇,但前端也可做體驗(yàn)上的優(yōu)化。 原因 當(dāng)使用外...
摘要:合理的優(yōu)化長列表,可以提升用戶體驗(yàn)。這樣保證了無論如何滾動(dòng),真實(shí)渲染出的節(jié)點(diǎn)只有可視區(qū)內(nèi)的列表元素。具體效果如下圖所示對(duì)于比無優(yōu)化的情況,優(yōu)化后的虛擬列表渲染速度提升很明顯。是基于來實(shí)現(xiàn)的,但是是一個(gè)維的列表,而不是網(wǎng)狀。 ??對(duì)于較長的列表,比如1000個(gè)數(shù)組的數(shù)據(jù)結(jié)構(gòu),如果想要同時(shí)渲染這1000個(gè)數(shù)據(jù),生成相應(yīng)的1000個(gè)原生dom,我們知道原生的dom元素是很復(fù)雜的,如果長列表...
摘要:合理的優(yōu)化長列表,可以提升用戶體驗(yàn)。這樣保證了無論如何滾動(dòng),真實(shí)渲染出的節(jié)點(diǎn)只有可視區(qū)內(nèi)的列表元素。具體效果如下圖所示對(duì)于比無優(yōu)化的情況,優(yōu)化后的虛擬列表渲染速度提升很明顯。是基于來實(shí)現(xiàn)的,但是是一個(gè)維的列表,而不是網(wǎng)狀。 ??對(duì)于較長的列表,比如1000個(gè)數(shù)組的數(shù)據(jù)結(jié)構(gòu),如果想要同時(shí)渲染這1000個(gè)數(shù)據(jù),生成相應(yīng)的1000個(gè)原生dom,我們知道原生的dom元素是很復(fù)雜的,如果長列表...
閱讀 2500·2021-11-23 09:51
閱讀 549·2019-08-30 13:59
閱讀 1855·2019-08-29 11:20
閱讀 2555·2019-08-26 13:41
閱讀 3268·2019-08-26 12:16
閱讀 756·2019-08-26 10:59
閱讀 3351·2019-08-26 10:14
閱讀 627·2019-08-23 17:21