React で 外部リソースをキャッシュする方法
概要
Http等で外部に問い合わせて取得した値を、View側でキャッシュする方法の解説です。
今回のユースケースは 一文字入力する毎にメンバー検索を行い、サジェッションを表示する場合を考えます。
新しい文字入力はサーバーサイドから取得する必要がありますが、文字を削除した場合は過去に問い合わせた内容を再度リクエストすることになります。
また、検索のView自体は常に表示されている訳ではなく、特定のView内に存在していると過程します。
つまり、新しい検索のキーワードならリクエストする。既知のキーワードなら過去の結果を利用する。
これが出来れば、リクエスト頻度を押さえつつ、ユーザー体験的にも貢献できると言えるでしょう。(リクエストにはどうしても待機時間が発生してしまうため)
実装
キャッシュ機構
内部にマップオブジェクトを作成し、View側にリクエストしたい関数を渡せるようにします。
コメントにもありますが、どこでも利用できてしまうと思わぬ事故が発生しかねない(メモリ圧迫や不整合)ので、
useCallbackを経由させることで、関数コンポーネントからしか呼べないようにしておきます。
// マップのデータ構造を使ってキャッシュを行う。
const cacheByMap = <K extends unknown, V extends unknown>(map: Map<K, V>) => async (
value: K,
requestFunc: (value: K) => Promise<V>
) => {
const cache = map;
const cacheValue = cache.get(value);
if (!cacheValue) {
const result = await requestFunc(value);
cache.set(value, result);
return result;
} else {
return cacheValue;
}
};
/**
* 外部へ通信を行う関数の入力値と、出力値をマップでキャッシュすることで、不要なI/Oを削減する関数。
* 一時的な用途を強制するため, あえてuseCallbackでView専用にし、アンマウントされれば開放されるようにしている。
* @param initValue
*/
export const useRequestWithCache = <K extends unknown, V extends unknown>(initValue?: Map<K, V>) => {
return useCallback(cacheByMap<K, V>(initValue ?? new Map<K, V>()), []);
};
利用する側 (関数コンポーネント)
初めの方で宣言しておき、あとはuseEffect内部などでリクエストする関数(例だとfindMembersByName
)を渡して利用するだけです。
外から利用する側は、キャッシュから取れたか、実際にリクエストしているかは分かりませんが、戻ってきた値を利用すれば良いだけなので、特に考えなくても良いでしょう。
const UserList = ({ userName }: { userName: string }) => {
const [users, setUsers] = useState<User[]>([]);
const requestWithCache = useRequestWithCache<string, User[]>();
useEffect(() => {
requestWithCache(userName, findMembersByName).then(setUsers);
}, [userName]);
return (
<View>
<Something />
</View>
)
}
上記の例ですと、userName に '太' (1文字目) が渡された場合キャッシュにヒットしませんからサーバーサイドに問い合わせし、ユーザー情報一覧を取得します。
2文字目 として '太郎' ときた場合も同様にサーバーサイドに問い合わせします。
そこでユーザーが1文字削除し、userName が '太' になった場合、前回の結果を利用した値を返し、サーバーサイドへは問い合わせしません。
そういった形で、地味にリクエスト数を削減 & レスポンスタイムを早く見せかけられるわけです。
その後ユーザーがこのViewを閉じるなどして、アンマウントされた場合は、もちろんキャッシュも破棄されますので、次回以降はリフレッシュされた値が取得できることになります。