react-native flatlist初探

前言

長列表或者無限下拉列表是最常見的應(yīng)用場景之一。RN 提供的 ListView 組件监婶,在長列表這種數(shù)據(jù)量大的場景下旅赢,性能堪憂。而在最新的 0.43 版本中惑惶,提供了 FlatList 組件煮盼,或許就是你需要的高性能長列表解決方案。它足以應(yīng)對大多數(shù)的長列表場景带污。

使用方法

FlatList 有三個(gè)核心屬性 data renderItem getItemLayout僵控。它繼承自 ScrollView 組件,所以擁有 ScrollView 的屬性和方法鱼冀。

renderItem

和 ListView 的 renderRow 類似报破,它接收一個(gè)函數(shù)作為參數(shù),該函數(shù)返回一個(gè) ReactElement千绪。函數(shù)的第一個(gè)參數(shù)的 item 是 data屬性中的每個(gè)列表的數(shù)據(jù)( Array<object> 中的 object) 充易。這樣就將列表元素和數(shù)據(jù)結(jié)合在一起,生成了列表。這里為了測試性能,放入了一個(gè)文本和圖片

renderItem({item, index}) {
        return <View style={styles.listItem}>
            <View style={styles.text}>
                <Text >{item.title}</Text>
            </View>
            <View style={styles.image}>
                <Image source={{uri:item.imgsource}} style={styles.image} resizeMode='stretch'></Image>
            </View>
        </View>;
    }

getItemLayout

可選優(yōu)化項(xiàng)歉提。但是實(shí)際測試中季惯,如果不做該項(xiàng)優(yōu)化,性能會(huì)差很多。所以強(qiáng)烈建議做此項(xiàng)優(yōu)化!如果不做該項(xiàng)優(yōu)化,每個(gè)列表都需要事先渲染一次改备,動(dòng)態(tài)地取得其渲染尺寸,然后再真正地渲染到頁面中蔓倍。

如果預(yù)先知道列表中的每一項(xiàng)的高度(ITEM_HEIGHT)和其在父組件中的偏移量(offset)和位置(index)绍妨,就能減少一次渲染润脸。這是很關(guān)鍵的性能優(yōu)化點(diǎn)。

getItemLayout={(data, index) => (
       console.log("index="+index),
       {length: itemHeight, offset: itemHeight * index, index}
)}

注意他去,這里有個(gè)坑毙驯,如果設(shè)置了getItemLayout,那么renderItem的高度必須和這個(gè)高度一樣灾测,否則加載一段列表后就會(huì)出現(xiàn)錯(cuò)亂和顯示空白爆价。
官方文檔介紹加入優(yōu)化項(xiàng)性能會(huì)提高很多,測試的時(shí)候發(fā)現(xiàn)加不加影響不大媳搪,可能是我打開方式不正確铭段,有待后續(xù)研究

完整代碼如下:

'use strict';
import React, {Component} from 'react';
import {
    FlatList,
    AppRegistry,
    StyleSheet,
    Text,
    View,
    Image,
} from 'react-native';

export default class ViewPager extends Component {

    constructor(props) {
        super(props);
        this.state = {
            listData: this.getData(0),
            myindex: 1,
        };
    }

    getData(index) {
        var list = [];
        for (let i = 0; i < 20; i++) {
            let imgsource;
            if (i % 5 == 0) {
                imgsource = 'http://photo.l99.com/bigger/01/1417155508319_k38f29.jpg';
            } else if (i % 5 == 1) {
                imgsource = 'http://img.tupianzj.com/uploads/allimg/160411/9-1604110SI5.jpg';

            } else if (i % 5 == 2) {
                imgsource = 'http://img.tupianzj.com/uploads/allimg/160411/9-1604110SH4.jpg';

            } else if (i % 5 == 3) {
                imgsource = 'http://img.tupianzj.com/uploads/allimg/160411/9-1604110SH6.jpg';

            } else if (i % 5 == 4) {
                imgsource = 'http://img.tupianzj.com/uploads/allimg/160411/9-1604110SH7.jpg';

            }
            list.push({title: 'title' + (i + (index * 20)), key: 'key' + (i + (index * 20)), imgsource: imgsource});
        }
        return list;
    }

    renderItem({item, index}) {
        return <View style={styles.listItem}>
            <View style={styles.text}>
                <Text >{item.title}</Text>
            </View>
            <View style={styles.image}>
                <Image source={{uri:item.imgsource}} style={styles.image} resizeMode='stretch'></Image>
            </View>
        </View>;
    }

    render() {
        return (
            <View style={styles.view}>
                <FlatList
                    data={this.state.listData}
                    renderItem={this.renderItem}
                    onEndReached={()=>{
                        if(this.state.myindex<2){
                      // 到達(dá)底部,加載更多列表項(xiàng)
                      this.setState({
                        listData: this.state.listData.concat(this.getData(this.state.myindex)),
                        myindex:this.state.myindex+1

                      });
                      }
                      console.log("onEndReached=" + this.state.listData.length);
                }}
                    refreshing={false}
                    onRefresh={() => {

                            this.setState({
                            listData: this.getData(0),
                            myindex:1,
                          });

                     console.log("onRefresh=" + this.state.listData.length);
                    }}
                    debug={true}
                    numColumns={1}

                    getItemLayout={(data, index) => (
                    // 120 是被渲染 item 的高度 ITEM_HEIGHT秦爆。
                    console.log("index="+index),
                    {length: itemHeight, offset: itemHeight * index, index}
                )}
                    ListFooterComponent={this.footerView}
                    onScroll={this._scrollSinkY}
                />
            </View>
        )
    }

    footerView() {

        return <View style={{flex:1,height:70,justifyContent:'center',alignItems:'center'}}>
            <Text>上啦加載更多</Text>
        </View>

    }
}

const itemHeight = 200;
const styles = StyleSheet.create({
    view: {
        flex: 1
    },
    listItem: {
        flexDirection: 'row',
        flex: 1,
        height: itemHeight,
        borderBottomWidth: 1,
        borderBottomColor: 'red'
    },
    image: {
        height: 180,
        width: 150,
    },
    text: {
        height: 180,
        width: 100,
    },

});
AppRegistry.registerComponent('ViewPager', () => ViewPager);

另外一個(gè)坑序愚,運(yùn)行的時(shí)候,加入上拉加載更多和下拉刷新后等限,多下拉幾次以后爸吮,上拉加載更多就不起作用了(觸發(fā)不了onEndReached方法),有可能是是我打開方式不對望门,歡迎各位大神指出我代碼的問題形娇。

源碼分析

FlatList 之所以節(jié)約內(nèi)存、渲染快筹误,是因?yàn)樗粚⒂脩艨吹降?和即將看到的)部分真正渲染出來了桐早。而用戶看不到的地方,渲染的只是空白元素厨剪。渲染空白元素相比渲染真正的列表元素需要內(nèi)存和計(jì)算量會(huì)大大減少哄酝,這就是性能好的原因。

FlatList 將頁面分為 4 部分祷膳。初始化部分/上方空白部分/展現(xiàn)部分/下方空白部分陶衅。初始化部分,在每次都會(huì)渲染钾唬;當(dāng)用戶滾動(dòng)時(shí),根據(jù)需求動(dòng)態(tài)的調(diào)整(上下)空白部分的高度侠驯,并將視窗中的列表元素正確渲染來抡秆。

895914763-58d48c7ddb570_articlex.jpeg
_usedIndexForKey = false;
const lastInitialIndex = this.props.initialNumToRender - 1;
const {first, last} = this.state;
// 初始化時(shí)的 items (10個(gè)) ,被正確渲染出來
this._pushCells(cells, 0, lastInitialIndex);
//  first 就是 在視圖中(包括要即將在視圖)的第一個(gè) item
if (!disableVirtualization && first > lastInitialIndex) {
  const initBlock = this._getFrameMetricsApprox(lastInitialIndex);
  const firstSpace = this._getFrameMetricsApprox(first).offset -
    (initBlock.offset + initBlock.length);
  // 從第 11 個(gè) items (除去初始化的 10個(gè) items) 到 first 渲染空白元素
  cells.push(
    <View key="$lead_spacer" style={{[!horizontal ? 'height' : 'width']: firstSpace}} />
  );
}
// last 是最后一個(gè)在視圖(包括要即將在視圖)中的元素吟策。
// 從 first 到 last 儒士,即用戶看到的界面渲染真正的 item
this._pushCells(cells, Math.max(lastInitialIndex + 1, first), last);
if (!this._hasWarned.keys && _usedIndexForKey) {
  console.warn(
    'VirtualizedList: missing keys for items, make sure to specify a key property on each ' +
    'item or provide a custom keyExtractor.'
  );
  this._hasWarned.keys = true;
}
if (!disableVirtualization && last < itemCount - 1) {
  const lastFrame = this._getFrameMetricsApprox(last);
  const end = this.props.getItemLayout ?
    itemCount - 1 :
    Math.min(itemCount - 1, this._highestMeasuredFrameIndex);
  const endFrame = this._getFrameMetricsApprox(end);
  const tailSpacerLength =
    (endFrame.offset + endFrame.length) -
    (lastFrame.offset + lastFrame.length);
   // last 之后的元素,渲染空白
  cells.push(
    <View key="$tail_spacer" style={{[!horizontal ? 'height' : 'width']: tailSpacerLength}} />
  );
}

既然要使用空白元素去代替實(shí)際的列表元素檩坚,就需要預(yù)先知道實(shí)際展現(xiàn)元素的高度(或?qū)挾?和相對位置着撩。如果不知道诅福,就需要先渲染出實(shí)際展現(xiàn)元素,在獲取完展現(xiàn)元素的高度和相對位置后拖叙,再用相同(累計(jì))高度空白元素去代替實(shí)際的列表元素氓润。_onCellLayout 就是用于動(dòng)態(tài)計(jì)算元素高度的方法,如果事先知道元素的高度和位置薯鳍,就可以使用上面提到的 getItemLayout 方法咖气,就能跳過 _onCellLayout 這一步,獲得更好的性能挖滤。

return (
    // _onCellLayout 就是這里的 _onLayout
    // 先渲染一次展現(xiàn)元素崩溪,通過 onLayout 獲取其尺寸等信息
  <View onLayout={this._onLayout}>
    {element}
  </View>
);
...
  _onCellLayout = (e, cellKey, index) => {
    // 展現(xiàn)元素尺寸等相關(guān)計(jì)算
    const layout = e.nativeEvent.layout;
    const next = {
      offset: this._selectOffset(layout),
      length: this._selectLength(layout),
      index,
      inLayout: true,
    };
    const curr = this._frames[cellKey];
    if (!curr ||
      next.offset !== curr.offset ||
      next.length !== curr.length ||
      index !== curr.index
    ) {
      this._totalCellLength += next.length - (curr ? curr.length : 0);
      this._totalCellsMeasured += (curr ? 0 : 1);
      this._averageCellLength = this._totalCellLength / this._totalCellsMeasured;
      this._frames[cellKey] = next;
      this._highestMeasuredFrameIndex = Math.max(this._highestMeasuredFrameIndex, index);
      // 重新渲染一次。最終會(huì)調(diào)用一次上面分析的源碼
      this._updateCellsToRenderBatcher.schedule();
    }
  };

簡單分析 FlatList 的源碼后斩松,后發(fā)現(xiàn)它并沒有和 native 端復(fù)用邏輯伶唯。而且如果有些機(jī)器性能極差,渲染過慢惧盹,那些假的列表——空白元素就會(huì)被用戶看到乳幸!

實(shí)測

性能確實(shí)很高,加載圖片加文章岭参,很流暢反惕。加載到1000條左右的時(shí)候,內(nèi)存占用大概30M(和圖片質(zhì)量有關(guān)系),cpu使用在停止時(shí)候0.5%左右演侯,加載時(shí)候12%左右姿染。

參考文章:https://segmentfault.com/a/1190000008589705

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市秒际,隨后出現(xiàn)的幾起案子悬赏,更是在濱河造成了極大的恐慌,老刑警劉巖娄徊,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闽颇,死亡現(xiàn)場離奇詭異,居然都是意外死亡寄锐,警方通過查閱死者的電腦和手機(jī)兵多,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來橄仆,“玉大人剩膘,你說我怎么就攤上這事∨韫耍” “怎么了怠褐?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長您宪。 經(jīng)常有香客問我奈懒,道長奠涌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任磷杏,我火速辦了婚禮溜畅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茴丰。我一直安慰自己达皿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布贿肩。 她就那樣靜靜地躺著峦椰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汰规。 梳的紋絲不亂的頭發(fā)上汤功,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音溜哮,去河邊找鬼滔金。 笑死,一個(gè)胖子當(dāng)著我的面吹牛茂嗓,可吹牛的內(nèi)容都是我干的餐茵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼述吸,長吁一口氣:“原來是場噩夢啊……” “哼忿族!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蝌矛,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤道批,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后入撒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隆豹,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年茅逮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了璃赡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡献雅,死狀恐怖碉考,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情惩琉,我是刑警寧澤豆励,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布夺荒,位于F島的核電站瞒渠,受9級特大地震影響良蒸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伍玖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一嫩痰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧窍箍,春花似錦串纺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至邪狞,卻和暖如春祷蝌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帆卓。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工巨朦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人剑令。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓糊啡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吁津。 傳聞我的和親對象是個(gè)殘疾皇子棚蓄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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

  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補(bǔ)...
    _Yfling閱讀 13,754評論 1 92
  • 深入JSX date:20170412筆記原文其實(shí)JSX是React.createElement(componen...
    gaoer1938閱讀 8,069評論 2 35
  • [文集目錄] 你過得好不好睛挚,跟我有幾毛? 不是嗎急黎?即使你腰殘萬罐扎狱,富甲一方,我也沒想高攀你勃教,向你借錢淤击;如果你窮困潦...
    飄揚(yáng)滴楊閱讀 226評論 2 1
  • 黑色的鳥張開黑夜的翅膀飛向白天 白色的鳥張開白晝的翅膀飛入黑夜
    文森林木閱讀 159評論 0 0
  • 這一章節(jié)應(yīng)該算是這本書的最后一章了,總結(jié)到這里故源,已經(jīng)總結(jié)了八篇污抬,感覺最后幾篇有一些啰嗦,朋友們請見諒。 一印机、為什么...
    踏雁尋花閱讀 4,690評論 0 5