ReactNativeのより良いリスト、FlatListについて。これまでのListViewとの違い。

  • 32
    いいね
  • 0
    コメント

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がヘビーなのでパフォーマンスの問題でリリースを見送ったりしてました。

4月-18-2017 19-45-31.gif

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