本文へスキップ

React Nativeにおけるより優れたリストビュー

·読了時間6分
Spencer Ahrens
Facebookソフトウェアエンジニア

コミュニティグループでのティザー発表の後、皆さんの中にはすでに新しいリストコンポーネントの一部を試している方もいると思いますが、本日正式に発表いたします!ListViewDataSource、古い行、無視されたバグ、過剰なメモリ消費はもうありません - 最新のReact Native 2017年3月リリース候補(0.43-rc.1)では、新しいコンポーネントスイートからユースケースに最適なものを選び、優れたパフォーマンスと機能をすぐに利用できます。

<FlatList>

これは、シンプルで高性能なリストのための主力コンポーネントです。データの配列とrenderItem関数を用意すれば、準備完了です。

<FlatList
data={[{title: 'Title Text', key: 'item1'}, ...]}
renderItem={({item}) => <ListItem title={item.title} />}
/>

<SectionList>

論理的なセクションに分割されたデータセット、セクションヘッダー(アルファベット順のアドレス帳など)、異種データとレンダリング(ボタンがいくつかあるプロフィールビュー、コンポーザー、フォトグリッド、フレンドグリッド、そしてストーリーのリストなど)をレンダリングしたい場合は、これが最適な方法です。

<SectionList
renderItem={({item}) => <ListItem title={item.title} />}
renderSectionHeader={({section}) => <H1 title={section.key} />}
sections={[ // homogeneous rendering between sections
{data: [...], key: ...},
{data: [...], key: ...},
{data: [...], key: ...},
]}
/>

<SectionList
sections={[ // heterogeneous rendering between sections
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
]}
/>

<VirtualizedList>

より柔軟なAPIを使用した内部実装。データが単純な配列ではない場合(不変リストなど)に特に便利です。

機能

リストは多くのコンテキストで使用されるため、新しいコンポーネントには、すぐに使える多くのユースケースに対応する機能が満載されています。

  • スクロール読み込み(onEndReached)。
  • プルダウン更新(onRefresh / refreshing)。
  • 設定可能な視認性(VPV)コールバック(onViewableItemsChanged / viewabilityConfig)。
  • 水平モード(horizontal)。
  • インテリジェントなアイテムとセクションのセパレーター。
  • 複数列のサポート(numColumns
  • scrollToEndscrollToIndexscrollToItem
  • より良いFlow型。

いくつかの注意点

  • コンテンツがレンダリングウィンドウからスクロールアウトすると、アイテムのサブツリーの内部状態は保持されません。すべてのデータは、アイテムデータまたはFlux、Redux、Relayなどの外部ストアにキャプチャされていることを確認してください。

  • これらのコンポーネントはPureComponentに基づいているため、propsが浅い等値のままであれば再レンダリングされません。renderItem関数が直接依存するものはすべて、更新後に===でないプロップとして渡すようにしてください。そうでなければ、UIは変更時に更新されない可能性があります。これには、dataプロップと親コンポーネントの状態が含まれます。例:

    <FlatList
    data={this.state.data}
    renderItem={({item}) => (
    <MyItem
    item={item}
    onPress={() =>
    this.setState(oldState => ({
    selected: {
    // New instance breaks `===`
    ...oldState.selected, // copy old data
    [item.key]: !oldState.selected[item.key], // toggle
    },
    }))
    }
    selected={
    !!this.state.selected[item.key] // renderItem depends on state
    }
    />
    )}
    selected={
    // Can be any prop that doesn't collide with existing props
    this.state.selected // A change to selected should re-render FlatList
    }
    />
  • メモリを制限し、スムーズなスクロールを有効にするために、コンテンツは非同期的にオフスクリーンでレンダリングされます。つまり、塗りつぶしの速度よりも速くスクロールして、一時的に空白のコンテンツが表示される可能性があります。これは、各アプリケーションのニーズに合わせて調整できるトレードオフであり、バックエンドでの改善に取り組んでいます。

  • デフォルトでは、これらの新しいリストは各アイテムのkeyプロップを探し、それをReactキーとして使用します。あるいは、カスタムkeyExtractorプロップを提供することもできます。

パフォーマンス

APIの簡素化に加えて、新しいリストコンポーネントには、行数に関係なくほぼ一定のメモリ使用量という、重要なパフォーマンスの強化も含まれています。これは、レンダリングウィンドウの外にある要素を仮想化することで行われ、コンポーネント階層から完全にアンマウントして、ReactコンポーネントからJSメモリと、シャドウツリーとUIビューからネイティブメモリを再利用します。これには、内部コンポーネントの状態は保持されないという欠点があります。そのため、**RelayやRedux、Fluxストアなど、コンポーネント自体以外の場所で重要な状態を追跡してください。**

レンダリングウィンドウを制限することで、Reactとネイティブプラットフォームによって実行する必要がある作業量(ビューのトラバーサルなど)も削減されます。100万個の要素の最後をレンダリングしている場合でも、これらの新しいリストでは、すべて要素を反復処理してレンダリングする必要はありません。scrollToIndexを使用して、過剰なレンダリングなしで中央にジャンプすることもできます。

また、アプリケーションの応答性を向上させるスケジューリングについても改善を行いました。レンダリングウィンドウの端にあるアイテムは、アクティブなジェスチャーやアニメーション、その他のインタラクションが完了した後に、低頻度で低い優先順位でレンダリングされます。

高度な使用方法

ListViewとは異なり、レンダリングウィンドウ内のすべてのアイテムは、プロップが変更されるたびに再レンダリングされます。ウィンドウ処理によりアイテム数が一定の数に減るため、多くの場合これで問題ありませんが、アイテムが複雑な場合は、Reactのパフォーマンスに関するベストプラクティスに従い、必要に応じてReact.PureComponentshouldComponentUpdateをコンポーネント内で使用して、再帰的なサブツリーの再レンダリングを制限する必要があります。

行の高さをレンダリングせずに計算できる場合は、getItemLayoutプロップを提供することで、ユーザーエクスペリエンスを向上させることができます。これにより、たとえばscrollToIndexを使用して特定のアイテムにスクロールするのがはるかにスムーズになり、コンテンツの高さをレンダリングせずに決定できるため、スクロールインジケーターのUIも向上します。

不変リストなどの代替データ型がある場合は、<VirtualizedList>を使用することをお勧めします。これは、任意のインデックスのアイテムデータの返却を可能にするgetItemプロップを受け取り、より緩やかなFlow型を持ちます。

特殊なユースケースの場合は、調整できるパラメータがいくつかあります。例えば、windowSizeはメモリ使用量とユーザーエクスペリエンスのトレードオフに使用できます。maxToRenderPerBatchは、描画速度と応答性の調整に使用でき、onEndReachedThresholdはスクロール読み込みのタイミングを制御するなど、他にも多くのパラメータがあります。

今後の作業

  • 既存サーフェスの移行(最終的にはListViewの非推奨化)。
  • 必要に応じて機能を追加していきます(お知らせください!)。
  • 固定セクションヘッダーのサポート。
  • パフォーマンスの最適化。
  • 状態を持つ関数型アイテムコンポーネントのサポート。