React NativeのFlatListで作る、無限スクロールのアプリ開発

どうもこんにちは、たくびー(@takubii)です。

最近、React nativeを使用したアプリの開発をよくおこなっています。
今回は私が開発中に採用した、無限スクロールの実装方法について詳しくご紹介します。
それぞれのコード部分について詳細な説明を挟んでいきますので、どうぞ最後までお付き合いください。

# データの作成
まず始めに、データ保持用とオフセット用のStateを作成します。
ここでは取得するデータの型は以下のように定義します。

```typescript
type ItemType = {
  id: number;
  text: string;
}
```

```typescript
const [items, setItems] = useState<Array<ItemType>>([]);
const [offset, setOffset] = useState(0);
```

# フェッチ関数の作成
次にデータのフェッチ用の関数を作成します。
この例ではAPIがページング機能を持っていることを前提としています。

以下のコードではデータをフェッチし、すでにあるアイテムの末尾に追加しています。
その後オフセットの値を追加し、次回のフェッチ時の開始位置を調整します。

```typescript
const fetchItems = async () => {
  try {
    // 今回は5つずつデータを取ってくるとします。
    const option = `size=5&offset=${offset}`;
    // fetch先のURLは適切なものに変えてください。
    const response = await fetch(`${URL}/api/items?${option}`);

    if (!response.ok) {
      return;
    }

    const data = await response.json();
    setItems(prevData => [...prevData, ...data]);
    setOffset(prevOffset => prevOffset + 1);
  } catch (error) {
    console.log(error);
  }
}
```

このままだとデータが何も無いので、コンポーネントの初回レンダリング時にデータを取得します。
これはuseEffectを使って行います。

```typescript
useEffect(() => {
  fetchItems();
}, []);
```

# FlatListによる実装
無限スクロールの鍵となる部分、FlatListを使ってアイテムが追加されるようにします。
それぞれのプロパティの役割については次のとおりです：
- data: アイテムのデータ、配列を受け取ります。
- renderItem: mapのような関数になっていて、itemがdataに渡した配列の1つ1つのアイテムです。返り値はJSXとなっています。今回は直書きしていますが関数として渡した方が可読性は上がります。
- keyExtractor: mapの場合はコンポーネントにkey属性を渡しますが、ここではその役割を担っています。返り値はstring型となっています。
- onEndReached: リストの末尾に達した時に呼び出す関数を指定します。基本的には追加でアイテムを取得する関数です。
- onEndReachedThreshold: 0 ~ 1の間の数値でどのくらいの距離でonEndReachedを呼び出すかを決められます。0が一番近くで、1が一番遠いです。

その他にも多くのプロパティがあるので公式サイトで使える物があるか確認してみてください。

https://reactnative.dev/docs/flatlist

```typescript
return (
  <View style={{flex: 1}}>
    <FlatList
      data={items}
      renderItem={({item}) => (
        <YourComponent text={item.text} />
      )}
      keyExtractor={item => item.id.toString()}
      onEndReached={fetchItems}
      onEndReachedThreshold={1}
    />
  </View>
);
```

renderItem内を関数として取り出す場合は以下のようになります。

```typescript
const renderItem = ({item}: {item: ItemType}) => (
  <View>
    <YourComponent text={item.text} />
    <OtherComponent {...item} />
  </View>
);

return (
  <View style={{flex: 1}}>
    <FlatList
      data={items}
      renderItem={renderItem}
      keyExtractor={item => item.id.toString()}
      onEndReached={fetchItems}
      onEndReachedThreshold={1}
      style={styles.flatListStyle}
    />
  </View>
);
```

以上のステップにより、無限スクロールの実装が可能になります。
最初は複雑な実装が必要かと思っていましたが、この機能がデフォルトで備わっているところなどReact nativeのComponentは便利な物が多いです。

# 最終的なコード
最終的なコードの全体像は以下のようになります。
参考にしていただければ幸いです。

```typescript
import React, { useEffect, useState } from 'react';
import { View, FlatList } from 'react-native';
import YourComponent from './YourComponent';  // あなたのコンポーネントをインポートします。
import OtherComponent from './OtherComponent';  // 必要な他のコンポーネントもインポートします。

type ItemType = {
  id: number;
  text: string;
}

const YourApp = () => {
  const [items, setItems] = useState<Array<ItemType>>([]);
  const [offset, setOffset] = useState(0);

  const fetchItems = async () => {
    try {
      // 今回は5つずつデータを取ってくるとします。
      const option = `size=5&offset=${offset}`;
      // あなたのAPIのURLに書き換えてください。
      const URL = "your API URL here";
      // fetch先のURLは適切なものに変えてください。
      const response = await fetch(`${URL}/api/items?${option}`);

      if (!response.ok) {
        return;
      }

      const data = await response.json();
      setItems(prevData => [...prevData, ...data]);
      setOffset(prevOffset => prevOffset + 1);
    } catch (error) {
      console.log(error);
    }
  }

  useEffect(() => {
    fetchItems();
  }, []);

  const renderItem = ({item}: {item: ItemType}) => (
    <View>
      <YourComponent text={item.text} />
      <OtherComponent {...item} />
    </View>
  );

  return (
    <View style={{flex: 1}}>
      <FlatList
        data={items}
        renderItem={renderItem}
        keyExtractor={item => item.id.toString()}
        onEndReached={fetchItems}
        onEndReachedThreshold={1}
      />
    </View>
  );
}

export default YourApp;
```

# 終わりに
今回はスマホアプリではよく見られる無限スクロールをReact nativeで実装する方法を紹介しました。
私自身のReact nativeによる開発経験はまだ浅いので、初歩的な技術を身につけることで進歩を感じています。
今後もさらに学んだことを共有していければと思っています。

それでは今回はこのあたりで終わりにしたいと思います。
また機会がありましたら、お会いしましょう。