摘要:合理的優(yōu)化長列表,可以提升用戶體驗。這樣保證了無論如何滾動,真實渲染出的節(jié)點只有可視區(qū)內(nèi)的列表元素。具體效果如下圖所示對于比無優(yōu)化的情況,優(yōu)化后的虛擬列表渲染速度提升很明顯。是基于來實現(xiàn)的,但是是一個維的列表,而不是網(wǎng)狀。
??對于較長的列表,比如1000個數(shù)組的數(shù)據(jù)結(jié)構(gòu),如果想要同時渲染這1000個數(shù)據(jù),生成相應(yīng)的1000個原生dom,我們知道原生的dom元素是很復(fù)雜的,如果長列表通過生成如此多的dom元素來實現(xiàn),很可能使網(wǎng)頁失去響應(yīng)。
??貫穿React核心的就是"virtual dom",我們同樣的可以通過用虛擬列表的方式來優(yōu)雅的優(yōu)化長列表
原生dom渲染長列表的缺陷
虛擬列表優(yōu)化長列表的原理
通過react-virtualized來優(yōu)化長列表
通過react-tiny-virtual-list來優(yōu)化長列表
本文的原文地址發(fā)布在我的博客中:
https://github.com/forthealll...
歡迎star和fork~
本文的用例的代碼地址為:
https://github.com/forthealll...
一、原生dom渲染長列表的缺陷??首先我們嘗試在React項目中,未做任何優(yōu)化一次性渲染1000個dom,每個dom包含一個img標(biāo)簽,原生dom本身是很復(fù)雜的對象,加上img標(biāo)簽后。渲染的效果如下圖所示:
??可以很明顯的看到白屏的時間很長,因為在React中,不做任何優(yōu)化,直接渲染這么包含1000個圖片的dom節(jié)點,即使React本身用了虛擬dom,但是在首次渲染的時候,是實實在在的生成了1000個真實的dom,我們可以查看網(wǎng)頁中的真實dom情況,如下所示:
??從上圖我們可以看出,是確確實實的生成了1000個真實的dom,進(jìn)入頁面后,需要渲染這1000個dom,因此白屏的時間很長。
??此外,在直接渲染1000個dom節(jié)點的頁面,觸發(fā)滾動事件,也會使得內(nèi)存用量增加,具體可以如下圖所示:
此外同時渲染很多dom節(jié)點,也會造成一下幾個問題:
容易失幀,因為渲染很慢,所以無法維持瀏覽器的幀率,主觀上會顯得頁面卡頓
網(wǎng)頁失去響應(yīng),事件等無法及時被觸發(fā)
??上述的效果都是在PC端展示的,對于特定的移動設(shè)備,直接無優(yōu)化的渲染長列表所造成的問題會更加的放大。長列表的渲染在移動端的很多場景會遇到,比如微博,feeds流中等等。合理的優(yōu)化長列表,可以提升用戶體驗。
二、虛擬列表優(yōu)化長列表的原理優(yōu)化長列表的原理很簡單,基本原理可以一句話概括:
用數(shù)組保存所有列表元素的位置,只渲染可視區(qū)內(nèi)的列表元素,當(dāng)可視區(qū)滾動時,根據(jù)滾動的offset大小以及所有列表元素的位置,計算在可視區(qū)應(yīng)該渲染哪些元素。
具體實現(xiàn)步驟如下所示:
首先確定長列表所在父元素的大小,父元素的大小決定了可視區(qū)的寬和高
確定長列表每一個列表元素的寬和高,同時初始的條件下計算好長列表每一個元素相對于父元素的位置,并用一個數(shù)組來保存所有列表元素的位置信息
首次渲染時,只展示相對于父元素可視區(qū)內(nèi)的子列表元素,在滾動時,根據(jù)父元素的滾動的offset重新計算應(yīng)該在可視區(qū)內(nèi)的子列表元素。這樣保證了無論如何滾動,真實渲染出的dom節(jié)點只有可視區(qū)內(nèi)的列表元素。
假設(shè)可視區(qū)內(nèi)能展示5個子列表元素,及時長列表總共有1000個元素,但是每時每刻,真實渲染出來的dom節(jié)點只有5個。
5.補(bǔ)充說明,這種情況下,父元素一般使用position:relative,子元素的定位一般使用:position:absolute或sticky
通過虛擬列表優(yōu)化后,同樣的顯示1000個包含圖片的dom,白屏?xí)r間會大大的減少。具體效果如下圖所示:
對于比無優(yōu)化的情況,優(yōu)化后的虛擬列表渲染速度提升很明顯。
三、通過react-virtualized來優(yōu)化長列表社區(qū)實現(xiàn)虛擬列表的React組件很多,較為常用的是react-virtualized和react-tiny-virtual-list,前一個較為全面,第二個較為輕量。接下來會分別來介紹這倆個React組件庫。
1、react-virtualized簡介react-virtualized是一個實現(xiàn)虛擬列表較為優(yōu)秀的組件庫,react-virtualized提供了一些基礎(chǔ)組件用于實現(xiàn)虛擬列表,虛擬網(wǎng)格,虛擬表格等等,它們都可以減小不必要的dom渲染。此外還提供了幾個高階組件,可以實現(xiàn)動態(tài)子元素高度,以及自動填充可視區(qū)等等。
react-virtualized的基礎(chǔ)組件包含:
Grid:用于優(yōu)化構(gòu)建任意網(wǎng)狀的結(jié)構(gòu),傳入一個二維的數(shù)組,渲染出類似棋盤的結(jié)構(gòu)。
List:List是基于Grid來實現(xiàn)的,但是是一個維的列表,而不是網(wǎng)狀。
Table:Table也是基于Grid來實現(xiàn),表格具有固定的頭部,并且可以在垂直方向上滾動
Masonry:同樣可以在水平方向,也可以在垂直方向滾動,不同于Grid的是可以自定義每個元素的大小,或者子元素的大小也可以是動態(tài)變化的
Collection:類似于瀑布流的形式,同樣可以水平和垂直方向滾動。
值得注意的是這些基礎(chǔ)組件都是繼承于React中的PureComponent,因此當(dāng)state變化的時候,只會做一個淺比較來確定重新渲染與否
。
除了這幾個基礎(chǔ)組件外,react-virtualized還提供了幾個高階組件,比如ArrowKeyStepper
、AutoSizer、CellMeasurer、InfiniteLoader等,本文具體介紹常用的AutoSizer、CellMeasurer和InfiniteLoader。
AutoSizer:使用于一個子元素的情況,通過AutoSizer包含的子元素會根據(jù)父元素Resize的變化,自動調(diào)節(jié)該子元素的可視區(qū)的寬度和高度,同時調(diào)節(jié)的還有該子元素可視區(qū)真實渲染的dom元素的數(shù)目。
CellMeasurer:這個高階組件可以動態(tài)的改變子元素的高度,適用于提前不知道長列表中每一個子元素高度的情況。
InfiniteLoader:這個高階組件用于Table或者List的無限滾動,適用于滾動時異步請求等情況
2、react-virtualized基礎(chǔ)組件的使用下面我們來介紹一下常用的基礎(chǔ)組件Grid、List。
(1)Grid所有基礎(chǔ)組件基本上都是基于Grid構(gòu)成的,一個簡單的Grid的例子如下:
import { Grid } from "react-virtualized"; const list = [ ["Jony yu", "Software Engineer", "Shenzhen", "CHINA", "GUANGZHOU"], ["Jony yu", "Software Engineer", "Shenzhen", "CHINA", "GUANGZHOU"], ["Jony yu", "Software Engineer", "Shenzhen", "CHINA", "GUANGZHOU"], ["Jony yu", "Software Engineer", "Shenzhen", "CHINA", "GUANGZHOU"], ["Jony yu", "Software Engineer", "Shenzhen", "CHINA", "GUANGZHOU"], ["Jony yu", "Software Engineer", "Shenzhen", "CHINA", "GUANGZHOU"] ]; function cellRenderer ({ columnIndex, key, rowIndex, style }) { return ({list[rowIndex][columnIndex]}) } render(, rootEl );
顯示的效果如下圖所示:
渲染網(wǎng)格也是只渲染可視區(qū)的dom節(jié)點,有個有趣的現(xiàn)象是滾動條的大小,這里Grid做了一個細(xì)節(jié)優(yōu)化,只有滾動的時候才會顯示滾動條,停止?jié)L動后會隱藏滾動條。
(2)List接著來舉例說明一下List的使用:
import { List } from "react-virtualized"; import loremIpsum from "lorem-ipsum" const rowCount = 1000; const list = Array(rowCount).fill().map(()=>{ return loremIpsum({ count: 1, units: "sentences", sentenceLowerBound: 3, sentenceUpperBound: 3 } }) function rowRenderer ({ key, index, isScrolling, isVisible, style }) { return ({list[index]}) } export default class TestList extends Component{ render(){ return} }
List的使用方法也是極簡,指定列表總條數(shù)rowCount,每一條的高度rowHeight以及每次渲染的函數(shù)rowRenderer,就可以構(gòu)建一個渲染列表。具體的效果如下圖所示:
2、react-virtualized高階組件的使用結(jié)合List來看看react-virtualized高階組件的使用。
(1)AutoSizer首先來看使用不使用AutoSizer的缺點,如下圖所示,List只能指定固定的大小,如果其所在的父元素的大小resize了,那么List是不會主動填滿父元素的可視區(qū)的:
從上圖可以看出來,List是無法自動填充父元素的。因此我們這里需要使用AutoSizer。AutoSizer的使用也很簡單,我們只需要在List的基礎(chǔ)上:
class TestList extends Component{ render(){ return} }{({ height, width }) => ( )}
效果如下圖所示:
上述可以看出來增加了AutoSizer可以動態(tài)的適應(yīng)父元素寬度和高度的變化。
但是也存在一個問題:
子元素太長,換行后改變了子元素的高度后無法子適應(yīng),也就是說僅僅通過基礎(chǔ)的組件List是不支持子元素的高度動態(tài)改變的場景。
(2)CellMeasurer為了解決上述的子元素可以動態(tài)變化的問題,我們可以利用高階組件CellMeasurer:
import { List,AutoSizer,CellMeasurer, CellMeasurerCache} from "react-virtualized"; const cache = new CellMeasurerCache({ defaultHeight: 30,fixedWidth: true}); function cellRenderer ({ index, key, parent, style }) { console.log(index) return (); } {list[index]}
對于需要渲染的List,如下所示:
class TestList extends Component{ render(){ return} }{({ height, width }) => ( )}
最后的結(jié)果如下所示:
上圖我們看出來,子列表元素的高度可以動態(tài)變化,通過CellMeasurer可以實現(xiàn)子元素的動態(tài)高度。
(3)InfiniteLoader最后我們來考慮這種無限滾動的場景,很多情況下我們可能需要分頁加載,就是常見的在可視區(qū)內(nèi)無限滾動的場景。react-virtualized提供了一個高階組件InfiniteLoader用于實現(xiàn)無限滾動。
InfiniteLoader的使用很簡單,只要按著文檔來即可,就是分頁的去在家下一頁,滾動分頁所調(diào)用的函數(shù)為:
function loadMoreRows ({ startIndex, stopIndex }) { return new Promise(function(resolve,reject){ resolve() }).then(function(){ //模擬ajax請求 let temList = Array(10).fill(1).map(()=>{ return loremIpsum({ count: 1, units: "sentences", sentenceLowerBound:3, sentenceUpperBound:3 }) }) list = list.concat(temList) }) }
最后的效果如下:
看起來跟基礎(chǔ)組件List一樣,其實唯一的區(qū)別就是會在滾動的時候自動執(zhí)行l(wèi)oadMoreRows函數(shù)去更新list
(4)總結(jié)通過基礎(chǔ)組件Grid、List以及高階組件AutoSizer、CellMeasurer和InfiniteLoader,已經(jīng)可以構(gòu)建出比較復(fù)雜的場景,但是有一個缺陷,就是CellMeasurer雖說一定程度上支持動態(tài)子元素的高度的變化,其實是一種估算,存在很多邊界情況,無法適應(yīng)于動態(tài)元素的場景,特別是文本節(jié)點較多導(dǎo)致的高度變化。但是對于圖片節(jié)點的動態(tài)高度支持沒有很大的問題。
舉例一種邊界情況,CellMeasurer無法支持文本動態(tài)高度的情況:
從上圖可以看到,慢慢縮小的過程中,如果縮的太小,并沒有動態(tài)的撐大子元素的高度,出現(xiàn)了文字的重疊。
四、通過react-tiny-virtual-list來優(yōu)化長列表react-tiny-virtual-list是一個較為輕量的實現(xiàn)虛擬列表的組件,不同于react-virtualized支持網(wǎng)格以及表格等渲染優(yōu)化。
react-tiny-virtual-list只支持列表,使用方便,其源碼也只有700多行。
使用極其簡單:
import VirtualList from "react-tiny-virtual-list"; const data = ["A", "B", "C", "D", "E", "F","A", "B", "C", "D", "E", "F","A", "B", "C", "D", "E", "F", "A", "B", "C", "D", "E", "F"]; export default class TinyVirtual extends Component { render(){ return // The style property contains the item"s absolute position Letter: {data[index]}, Row: #{index}} /> } }
最后的渲染結(jié)果也是相似的,也可以支持無限滾動等等。
但是react-tiny-virtual-list有一個致命的缺點:
完全不支持子元素的動態(tài)高度或者寬度
五、總結(jié)本文介紹了虛擬列表的優(yōu)化的原理,以及常用的React可以優(yōu)化虛擬列表的組件庫。在接下來的文章中,會具體的介紹react-tiny-virtual-list和react-virtualized的源碼,敬請期待。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/114347.html
摘要:合理的優(yōu)化長列表,可以提升用戶體驗。這樣保證了無論如何滾動,真實渲染出的節(jié)點只有可視區(qū)內(nèi)的列表元素。具體效果如下圖所示對于比無優(yōu)化的情況,優(yōu)化后的虛擬列表渲染速度提升很明顯。是基于來實現(xiàn)的,但是是一個維的列表,而不是網(wǎng)狀。 ??對于較長的列表,比如1000個數(shù)組的數(shù)據(jù)結(jié)構(gòu),如果想要同時渲染這1000個數(shù)據(jù),生成相應(yīng)的1000個原生dom,我們知道原生的dom元素是很復(fù)雜的,如果長列表...
摘要:并總結(jié)經(jīng)典面試題集各種算法和插件前端視頻源碼資源于一身的文檔,優(yōu)化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快速搭建項目。 本文是關(guān)注微信小程序的開發(fā)和面試問題,由基礎(chǔ)到困難循序漸進(jìn),適合面試和開發(fā)小程序。并總結(jié)vue React html css js 經(jīng)典面試題 集各種算法和插件、前端視頻源碼資源于一身的文檔,優(yōu)化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快...
摘要:并總結(jié)經(jīng)典面試題集各種算法和插件前端視頻源碼資源于一身的文檔,優(yōu)化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快速搭建項目。 本文是關(guān)注微信小程序的開發(fā)和面試問題,由基礎(chǔ)到困難循序漸進(jìn),適合面試和開發(fā)小程序。并總結(jié)vue React html css js 經(jīng)典面試題 集各種算法和插件、前端視頻源碼資源于一身的文檔,優(yōu)化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快...
閱讀 887·2023-04-26 00:37
閱讀 750·2021-11-24 09:39
閱讀 2181·2021-11-23 09:51
閱讀 3877·2021-11-22 15:24
閱讀 767·2021-10-19 11:46
閱讀 1891·2019-08-30 13:53
閱讀 2456·2019-08-29 17:28
閱讀 1343·2019-08-29 14:11