サイズ調整のワークアラウンドもネイティブモジュールも不要——React Nativeで高速なリスト
React Nativeで複雑なインターフェースを構築したことがある人なら、リストに苦労した経験があるでしょう。標準のFlatListは、長いリストでFPSが低下し、メモリを消費します。ShopifyのFlashListはコンポーネントの再利用により顕著に高速ですが、テキスト量や画像が異なる投稿のように高さが予測できない要素を与えると、リストがセルサイズを estimatedItemSize を通じて推測しようとしたときに、高速スクロール中の小さなフリーズやレイアウトのジッターが発生し始めます。
最近、LegendAppチームはLegend Listライブラリをリリースしました。これは、この動的サイズ調整の問題を正確に解決する、TypeScriptのみで書かれたリストコンポーネントで、ネイティブコードは1行も使用していません。
コンセプト
このプロジェクトの主な目標は、熟悉されたAPIを持つFlatListとFlashListの代替品を提供することですが、可変高さ要素でのパフォーマンス低下はありません。開発者は意図的に純粋なJavaScriptとTypeScriptのみを使用しました。ネイティブモジュールをリンクする必要はありません。これにより、Expo GoやWebバージョン向けのビルドが簡素化され、React Nativeバージョンを更新する際の何かを壊すリスクも軽減されます。
このプロジェクトは軽量で、ドロップイン置換として機能します。すでにFlatListで書かれた画面を持っている場合、基本的な移行はインポートとコンポーネント名を変更するだけです。
ライブラリできること
開発者はモバイルアプリのリストにおける典型的な問題に集中しました。以下に、4つの顕著に便利な機能を示します:
箱から出してすぐに動的高さに対応。カードの高さを正確に事前計算したり、おおよその推定値を渡したりする必要はありません。リストはレンダリングの遅延なしでコンテンツサイズに自動的に適応します。
適切なチャットインターフェース。メッセージ画面を作成するための古典的なコツは、リストを
invertedで反転させることです。これにより、キーボードアニメーション、反転スクロール、位置決めで奇妙なバグが発生します。Legend Listには、反転なしでコンテンツを底部に向けるalignItemsAtEndプロパティと、新しいデータが到着したときにスクロールを最後のメッセージに維持するmaintainScrollAtEndがあります。双方向無限スクロール。上下方向へのデータ読み込みが、白い画面フラッシュやビューポート位置の急激なジャンプなしで行われます。
制御されたセルリサイクル。
recycleItemsプロパティを通じて、コンポーネントの再利用を有効または無効にできます。リスト要素の内部に複雑なローカルuseStateが保存されている場合、再利用は他の人の状態を表示する不快な副作用につながることがあります。Legend Listでは、安全のためにリサイクルはデフォルトで無効になっていますが、最大速度を得るために1行で有効にできます。
コードでの外観
インストールは標準的です:
npm install @legendapp/list
# или через bun / yarn
bun add @legendapp/list
runtimeサブフォルダからコンポーネントをインポートし、熟悉されたプロパティを渡します:
import React, { useRef } from "react"
import { View, Image, Text, StyleSheet } from "react-native"
import { LegendList, LegendListRef, LegendListRenderItemProps } from "@legendapp/list/react-native"
interface PostItem {
id: string
author: string
content: string
photoUrl?: string
}
export const FeedScreen = ({ posts }: { posts: PostItem[] }) => {
const listRef = useRef<LegendListRef | null>(null)
const renderItem = ({ item }: LegendListRenderItemProps<PostItem>) => {
return (
<View style={styles.card}>
<Text style={styles.author}>{item.author}</Text>
<Text style={styles.text}>{item.content}</Text>
{item.photoUrl && (
<Image source={{ uri: item.photoUrl }} style={styles.image} />
)}
</View>
)
}
return (
<LegendList
data={posts}
renderItem={renderItem}
keyExtractor={(item) => item.id}
recycleItems={true}
maintainVisibleContentPosition
ref={listRef}
/>
)
}
const styles = StyleSheet.create({
card: { padding: 16, borderBottomWidth: 1, borderColor: "#eee" },
author: { fontWeight: "bold", marginBottom: 4 },
text: { fontSize: 14, lineHeight: 20 },
image: { width: "100%", height: 200, marginTop: 8, borderRadius: 8 },
})
maintainVisibleContentPosition プロパティは、表示領域の上の要素のサイズが変更された場合、または新しいデータが到着した場合に、画面上のコンテンツを維持します。
制限事項と計画
このリポジトリはまだ若く、2024年末に登場しました。ロードマップでは、作者たちはまだ何が含まれていないかを率直に述べています:
- メーソンリーグリッド(高さの異なるタイル)
- スティッキーヘッダー
- カラムスパン
- 個別のアイテムタイプ(
getItemType)
画面にスティッキーセクションを含む複雑なグリッドが依存している場合、移行するにはまだ早すぎます。
誰が試すべきか
このライブラリは、チャット、ニュースフィード、コメントリストでのインターフェースジャンプにうんざりしている人に便利です。FlashListが動的カードでジッターが発生し、FlatListが必要なスムーズさを提供しない場合は、テストブランチに @legendapp/list をインストールしてください。コンポーネント構造を変更したり、ネイティブラッパーを書いたりする必要がないため、確認するのに数分しかかかりません。
関連プロジェクト