メインコンテンツへスキップ

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)。
  • Pull to refresh(onRefresh / refreshing)。
  • 設定可能な視認性(VPV)コールバック(onViewableItemsChanged / viewabilityConfig)。
  • 水平モード(horizontal)。
  • インテリジェントなアイテムとセクションのセパレータ。
  • 複数カラムのサポート(numColumns)。
  • scrollToEndscrollToIndex、およびscrollToItem
  • より良い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の非推奨化)。
  • 必要性を見聞きするにつれて、より多くの機能を追加(ぜひお知らせください!)。
  • スティッキーなセクションヘッダーのサポート。
  • さらなるパフォーマンスの最適化。
  • 状態を持つ関数型アイテムコンポーネントのサポート。