はじめに
前回書いた記事で、FlashListはRecyclerListViewの機能であるセルリサイクリングを使用していることが分かりました。
今回はその機能が実際にどのように作用しているのか確認していきます。
実際に動かしてみる
コードはこちら。
スマホアプリのExpo Goを使えば、実機で動作を確認することが出来ます。
以下は簡略化したコードになります。
const App = () => {
const data = useMemo(() => generateRandomColorList(), []);
const renderItem = useCallback(
({ item }) => {
return <ListItem item={item} />;
},
[width]
);
return (
<FlashList
data={data}
renderItem={renderItem}
pagingEnabled
horizontal
/>
);
};
const ListItem = ({ item }) => {
const [visibleIcon, setVisibleIcon] = useState(false);
const handlePress = useCallback(() => {
setVisibleIcon((prev) => !prev);
}, []);
return (
<ScrollView>
<Text style={{ color: item.color }}>{item.color}</Text>
<Button title="Press" onPress={handlePress} />
{visibleIcon ? <ThumbsUpIcon /> : null}
</ScrollView>
);
};
FlashListで使用するdataは、300個のランダムなカラーコードの配列です。
dataのカラーコードを受け取ってレンダリングされるアイテムコンポーネントでは、内部でScrollViewを使用しており縦スクロールが可能です。
また、[Press]ボタンを押すと がボタン下に表示されるように、アイテムコンポーネント内でvisibleIconというstateを宣言しています。
なにはともあれ、実際の挙動を見てみましょう。
最初のページでは最下部へスクロールし、2つめのページではボタンを押して を表示しました。
その後横スクロールを続けていくと、カラーコードは異なるものの、スクロール済みのページやが表示されているページが何度も繰り返されています。
FlatListではこのような現象は起きないのですが、FlashListではビューポートから外れるコンポーネントを再利用して表示しているため、このような挙動が起きます。
どう対応していくか
主な対応策としては下記になります。
- アイテムコンポーネント内でStateを持たず、外部から注入する。
- propsから渡されるitemの変更を検知してコンポーネントの状態やStateを初期化する。
1. アイテムコンポーネント内でStateを持たず、外部から注入する。
この対応はシンプルで、親コンポーネントでStateの管理を行います。
今回の例では、カラーコードの配列のみを扱っているdataの各アイテムデータに対してvisibleIconのプロパティを追加することになります。
handlePressなどのhandlerはextraDataというpropsに渡す方法もあります。
2. propsから渡されるitemの変更を検知してコンポーネントの状態やStateを初期化する。
このパターンでは、refやグローバル変数などを使用してitemの変更を検知し、初期化を行います。
例えば下記のような実装です。
const ListItem = ({ item }) => {
const scrollViewRef = useRef(null);
const [visibleIcon, setVisibleIcon] = useState(false);
const lastItemKey = useRef(item.key);
if (item.key !== lastItemKey.current) {
lastItemKey.current = item.key;
setVisibleIcon(false);
scrollViewRef.current?.scrollTo({x: 0, y: 0, animated: false});
}
...
return (
<ScrollView
ref={scrollViewRef}
...
/>
refで変更前のitemを保持することでpropsから渡されるitemと比較し、
異なるitemが渡される(コンポーネントがリサイクルされる)タイミングで、visiableIconとScrollViewのスクロール状態を初期化することで、コンポーネントのリサイクルが起きていないように見せています。
業務などで対応する場合には、1と2を合わせて対応するケースが多くなりそうですね。
ついでに実際に対応してみた挙動も見てみましょう。
コードはこちら