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

React Nativeでのより良いリストビュー

·6分で読めます
Spencer Ahrens
Facebook ソフトウェアエンジニア

多くの方は、コミュニティグループでのティーザー発表の後、すでに新しいListコンポーネントを使い始めているかもしれませんが、本日、正式に発表します!もう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がシャローイコール(shallow-equal)のままである場合、再レンダリングされません。renderItem関数が依存するすべてのものが、更新後に===でないpropとして直接渡されていることを確認してください。さもないと、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の非推奨化)。
  • 必要性を見聞きするにつれて、より多くの機能を追加(ぜひお知らせください!)。
  • スティッキーなセクションヘッダーのサポート。
  • さらなるパフォーマンスの最適化。
  • 状態を持つ関数型アイテムコンポーネントのサポート。