ReactNative0.43(2017-04-03)より、FlatListが新しいcomponentとして追加されました。
FlatListにはこれまでのListViewで解決できなかった問題にリーチできるように設計されてます。特にパフォーマンス周りの問題です。AndroidにおけるRecylcerViewの登場のような出来事だと思っています。
具体的に嬉しいのはonViewableItemsChanged
の追加です。ListViewでは同等のプロパティがiOSのみでのサポートでしたが、FlatListではAndroidでも動くらしいです(まだiOSでしか見てないです)。画面に表示されている行(item)を取得するが出来るので、各種パフォーマンス改善に役立てる事が出来ます。
ListViewに過去バージョンごとに追加されていったapiについても一通り既に実装されていますが、セクションヘッダーはありません。セクションヘッダーが欲しいなら<SectionList>
を使ってね、ということでした。
待望のonViewableItemsChanged
は最後に書くとして、まずは既存アプリのListViewを書き換えてみたのでdiffをみてみましょう。
ListViewとの違い
ListViewの場合
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderRow.bind(this)}
renderHeader={this._renderHeader.bind(this)}
renderFooter={this._renderFooter.bind(this)}
enableEmptySections={true}
onEndReachedThreshold={100}
onEndReached={this._onEndReached.bind(this)}
refreshing={this.state.refreshing}
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._reload.bind(this)}
/>
}
/>
)
}
FlatListの場合
render() {
return (
<FlatList
data={this.state.dataSource}
renderItem={this._renderItem.bind(this)}
ListHeaderComponent={() => this._renderHeader.call(this)}
ListFooterComponent={() => this._renderFooter.call(this)}
onEndReachedThreshold={100}
onEndReached={this._onEndReached.bind(this)}
onViewableItemsChanged={this._onViewableItemsChanged.bind(this)}
refreshing={this.state.refreshing}
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._reload.bind(this)}
/>
}
/>
)
}
全てのプロパティを使ってないですが、基本的な部分は使ってると思います。
違いがあるのは最初の4つのプロパティです。
dataSource => data
表示するデータを渡すプロパティ。
ListViewの時はdataSourceに変更を検知するDataSource.cloneWithRows
関数を通したオブジェクトを渡してましたが、FlatListのdataにはarrayをそのまま渡します。ただし、各itemにkey propertyを持たせてね、ということでした。
data={[{key: 'a'}, {key: 'b'}]}
renderRow => renderItem
itemをrenderする関数を渡します。
RowではなくItemなのはFlatListは正確にはテーブルを表示するためのコンポーネントではないからだと思います。iOSでいうCollectionViewをイメージすると良いと思います。一行に何アイテム表示するかを設定するnumColumns
というプロパティもあります。
ちなみに渡されるパラメーターにも少し違いがありました。
以下のように書き換えました。
renderRow(item, index) {
return <ItemComponent item={item} />
}
renderItem(data) {
let { item, index } = data;
return <ItemComponent item={item} />
}
renderHeader, renderFooter
HeaderとFooterはrenderする関数を渡すものからコンポーネント自体を渡すものに変わっているので、最初のコードにあるように書き換えました。
その他
それ以外は元よりScrollViewから移譲されているものであったりするので、そのままでいけました。
パフォーマンス改善
onViewableItemsChanged
を利用してレンダリングコストの改善を試してみます。
onViewableItemsChanged
に以下のような関数を渡します。
_onViewableItemsChanged(info) {
let { viewableItems, changed } = info;
this.setState({viewableItemIndices: viewableItems.map((item) => item.index)})
}
表示されている行のindexをstateで管理できるようになりました。
次にrenderItem
を修正します。
renderItem(data) {
let { item, index } = data;
if(this.state.viewableItemIndices.indexOf(index) < 0) return <View style={{height: 800}}/>;
return <ItemComponent item={item} />
}
要するに見えてないitemは代わりに空のViewをrenderしてしまおうという作戦です。
表示時とViewの大きさが変わるとガタガタするので適当なサイズを指定したViewを表示しましょう。
FlatListはgetItemLayout
にサイズの取得方法を指定することもできるようなので、そちらで指定しても良いかもしれません。
これで見えてないitemは空のViewが表示されるようになりました。
各itemのレンダリングコストが高いアプリならかなりの改善が見込めるのではないでしょうか。
夢が広がる
Listのパフォーマンスはみんな困ってたみたいで、
これまでもRecyclerView的なComponentを有志がオープンソースで作ろうとしているを何度か見ました。なので結構、万を持しての登場だと思います。
自身も短歌アプリで実験的に各行の文字がアニメーションするなどフリーダムな機能を作ってたはいいものの、各itemがヘビーなのでパフォーマンスの問題でリリースを見送ったりしてました。
FlatListにすることでサクサク動きました。実験なのでホントにリリースするかはやっぱり別の問題です\(^o^)/
以上です。
参考
FlatList Document
https://facebook.github.io/react-native/docs/listview.html
FlatListについての公式blog
https://facebook.github.io/react-native/blog/2017/03/13/better-list-views.html
短歌をはじめたいなら「うたよみん」で
https://itunes.apple.com/lu/app/minnano-duan-ge-tou-gaokomyuniti/id675671254?l=fr&mt=8