React.Suspense とはいったいなんぞや
導入
React
にRedux
を組み込んだ場合「データが取得されるまでローディング画面を表示したい」と思うことは多い。
それを解決するために、store
にローディング用のフラグをはやしたり、container
のstate
にフラグをはやしたりと、なんとも面倒な管理をしないといけなくなる。
とはいえ、store
にフラグをはやすのはなんとも不格好だし、container
のstate
もダサい、そもそもローディング用のフラグなんて扱いたくない。
そこでふと、「React.Suspense
でうまいこと解決できないものか?」と思うわけです。
結論
無理だった。
実装
すげー適当だけど、こんな感じ。
import React, { FC, Suspense, useCallback, useState } from "react";
import Fuga from "./Fuga";
const Hoge: FC = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, [setCount]);
const [cache, setCache] = useState<string | undefined>();
return (
<div>
<Suspense fallback={<div>loading...</div>}>
<Fuga cache={cache} count={count} setCache={setCache} />
</Suspense>
<button onClick={handleClick}>add</button>
</div>
);
};
export default Hoge;
import React, { FC, useCallback, useMemo } from "react";
export type FugaProps = {
cache?: string;
count: number;
setCache: (cache?: string) => void;
};
const Fuga: FC<FugaProps> = ({ cache, count, setCache }) => {
const getData = useCallback<() => string>(() => {
if (cache === undefined) {
const handleCache = async () => {
const data = await new Promise<string>((resolve) => {
setTimeout(() => {
// resolve(undefined);
resolve("loaded");
}, 1000);
});
setCache(data);
};
throw handleCache();
}
return cache;
}, [cache, setCache]);
const data = useMemo(() => getData(), [getData]);
return (
<div>
<div>{`data: ${data}`}</div>
<div>{`count: ${count}`}</div>
</div>
);
};
export default Fuga;
あんまりデバッグしてないので、間違えてる箇所多いと思います。
イメージということで一つ。
解説
React.Suspense
とは、その子コンポーネントのrender
関数が実行されるまでfallback
を表示するコンポーネント、という認識を持ってます。
Fuga
コンポーネント内の promise 部分が api の呼び出しに該当する箇所で、redux
だと同期的に呼び出すため、ここが解決できないです。
React.Suspense
のポイントは言わずもがなthrow
部分で、throw
を返すと関数がもう一度実行されるみたいです、初めて知りました。
例外を再発生させる、のくだりに書かれています。
そのため、先のコードでresolve(undefined)
にしてやると、1 秒ごとにずっとgetData
内の関数が実行され続け、ずーっとfallback
が表示され続けます。
再度結論
redux
のaction
は同期的に呼び出すため、組み込むことは不可能、多分。
やるとしたら、api の呼び出しをcontainer
自身で行う必要がある。
余談
React.Suspense
の話をすると、必ず話題に上がるReact.lazy
ですが、これも非同期にファイルを読み込むからセットみたいな感じなんだなーと、妙に納得しました。