はじめに
今回は皆さんお待ちかねの(?) use(promise)
の使い方第1回目です。
「Svelte でコンポーネントを作り React で糊付けする」シリーズは、惜しまれながらも(?)一応完結したことにしまして、同シリーズを連載中に「React での Async の基本からやり直した方がよいな」と感じたこともあったことから、この記事を書くことにしました。
それではさっそく始めましょう。
use(promise)
の基本形
いつものペライチ React なソースです。
VSCode の Live Server Extension などでお試しできます。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1, width=device-width" />
</head>
<body>
<div id="app"></div>
<script type="module">
////////////////////////////////////////////////////////////////////////////////
import React from "https://esm.sh/react@19?dev";
import ReactDOM from "https://esm.sh/react-dom@19?dev";
import ReactDOMClient from "https://esm.sh/react-dom@19/client?dev";
// fetcher
const getResource = async (q) => {
const response = await fetch(`https://dummyjson.com/products/search?q=${q}`);
// if (!response.ok) throw new Error(`dummyjson API error: ${response.status}`); // 今回はエラー処理は省略します
const data = await response.json();
return data.products
}
// fetcher を実行して Promise を取得し、そのタスクをスケジューリングする
const rss = getResource('');
//
const App = props => {
// <Suspense fallback={<div>Now Loading...</div>}>
// <MyComponent />
// </Suspense>
return React.createElement(React.Suspense, {fallback: React.createElement('div', null, 'Now Loading...'), },
React.createElement(MyComponent),
);
}
//
const MyComponent = props => {
const data = React.use(rss);
const rows = data.map((item, index) => {
// <li key={item.id}>{`ほにゃらら`}</li>
return React.createElement('li', {key: item.id}, `${item.title} (${item.price} $)`);
});
// <div>
// <h1>Products</h1>
// <ul>{rows}</ul>
// </div>
return React.createElement('div', null,
React.createElement('h1', null, 'Products'),
React.createElement('ul', null, rows)
);
}
//
const root = ReactDOMClient.createRoot(document.getElementById("app"));
root.render(React.createElement(App));
////////////////////////////////////////////////////////////////////////////////
</script>
</body>
</html>
解説
// fetcher
const getResource = async (q) => {
const response = await fetch(`https://dummyjson.com/products/search?q=${q}`);
// if (!response.ok) throw new Error(`dummyjson API error: ${response.status}`);
const data = await response.json();
return data.products
}
// fetcher を実行して promise を取得し、そのタスクをスケジューリングする
const rss = getResource('');
まずは、fetcher を定義します。fetcher についてはよくある REST API の呼び出しですので特に難しいところはないはずです。
なお、今回エラー処理は割愛させていただきます。
次にソースコメントにある通り「fetcher を実行して promise を取得し、そのタスクをスケジューリング」します。これ自体は async function を await なしで呼び出した場合の通常動作に過ぎませんので、その点が理解できない方は、上級 SE に確認してください。
特に、この時点で「タスクをスケジューリング」する、というのが重要です。今回の例をペライチではなく実践的な独立した js モジュールであると考えた場合、モジュールがインポートされたときに一度だけこの処理が走ることになるわけですが、モジュールレベルで「タスクをスケジューリング」するのが重要、といいたいわけではなく、後ほどまた説明しますが、コンポーネントが use(promise)
を実行する前に「タスクをスケジューリング」しておくというのが重要です。
この、コンポーネントが use(promise)
を実行する前に「タスクをスケジューリング」しておくことを、render-as-you-fetch といいます。直訳すると「取得しながら描画する」ですから、必ず fetch が render よりも先でなければなりません
上記の点は use(promise)
を理解するうえで本当に基礎の基礎ですので、理解してから先に進んでください。実は私も時々この基礎を忘れてしまうことがあり何度も戻ってくる場所です。これらの点についてけして分かったふりをせず分かるまで10000回素振りして体に叩き込んでください。これが感覚としてしみ込んでいるかどうかで use(promise)
マスターといえるかどうかが決まるといっても過言ではありません。
const data = React.use(rss);
ここまできちんと読み進められてきた方ならお茶の子さいさいだと思いますが、rss
はなんでしょう?
そうです、「既にタスクとしてスケジューリングされた promise 」ですよね。ハズレた方は振出しに戻ってください。
少しだけ use(promise)
の内部動作を説明しますと、rss
はすでに説明した通り、「既にタスクとしてスケジューリングされた Promise 」なわけですが、まだそのタスクが完了していない場合は、例外をスローするため、MyComponet
はマウントされずに、Suspense の fallback
prop で指定した ReactElement が表示されます。タスクが完了していた場合は、getResource()
の戻り値をリアクティブデータとして返し、MyComponent
がマウントされ描画されます。
なお、絶対にやってはいけないパターンは ↓ です。
const data = React.use(getResource(''));
↑のパターンの問題点:
- コンポーネントが再レンダリングされる度に新しいタスクが作成されてしまう。
- レンダー中に Side Effect を実行している、すなわち pure ルール違反。
- 「タスクをスケジューリング」するのが遅すぎる、すなわち render-as-you-fetch とは言えないとして動作が不安定になる可能性あり。
なお、useState
Hook のように、仮に use(() => getResource(''))
のような lazy 初期化が許されていたとしても、2, 3 には反します(lazy の一般的な日本語訳である「遅延」には「無駄な処理をしない」というニュアンスがないため私はあえて lazy を使うようにしています)。先ほど念を押したように、 use(promise)
では、コンポーネントが use(promise)
を実行する前に必ず「タスクをスケジューリング」すること、すなわち render-as-you-fetch 、これを肝に銘じてください。
const App = props => {
// <Suspense fallback={<div>Now Loading...</div>}>
// <MyComponent />
// </Suspense>
return React.createElement(React.Suspense, {fallback: React.createElement('div', null, 'Now Loading...'), },
React.createElement(MyComponent),
);
}
Supense コンポーネントの子供として、内部で use(promise)
を使用しているコンポーネント(ここでは MyComponet
) を配置します。
なお、言うまでもないかもしれませんが、App
コンポーネントで use(promise)
を直接使用するとうまくいきません。理由は先ほど説明したように、 use(promise)
は、まだそのタスクが完了していない場合に例外をスローするからです。App
コンポーネントで use(promise)
が直接実行された場合、タスク未完了時に App
コンポーネント自体がマウントされず fallback
を表示するべき本人も不存在という、わけのわからないことになってしまいます。
以上が use(promise)
の基本形です。あとはその応用形に過ぎませんので この基本形を10000回素振りして体に完璧に叩き込んでください。
もし、今後 use(promise)
を使っていて何らかの疑問が生じた場合はこの基本形に戻ってください。冷静になれば必ずミスに気付けます。
use(promise)
の応用形(Refetch)
検索条件を変えるなど Refetch が必要になる場合は、rss
をリアクティブデータとする必要があります。
use(promise)
の使い方として、いきなりこの応用形から紹介する記事が多いことが、「 use(promise)
は難しい」と誤解させる原因ではないかと私は考えております。何度でも言いますが、もし分からないことがあったら基本形に戻ってみてください。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1, width=device-width" />
</head>
<body>
<div id="app"></div>
<script type="module">
////////////////////////////////////////////////////////////////////////////////
import React from "https://esm.sh/react@19?dev";
import ReactDOM from "https://esm.sh/react-dom@19?dev";
import ReactDOMClient from "https://esm.sh/react-dom@19/client?dev";
// fetcher
const getResource = async (q) => {
const response = await fetch(`https://dummyjson.com/products/search?q=${q}`);
// if (!response.ok) throw new Error(`dummyjson API error: ${response.status}`); // 今回はエラー処理は省略します
const data = await response.json();
return data.products
}
// fetcher を実行して Promise を取得し、そのタスクをスケジューリングする
const rss = getResource('');
//
const App = props => {
// resource を Suspense の外に置くのが安全といえば安全(React18では必須)
// × const [rss, setrss] = React.useState(() => getResource(''));
const [_rss, setrss] = React.useState(rss);
// <Suspense fallback={<div>Now Loading...</div>}>
// <MyComponent rss={_rss} setrss={setrss} />
// </Suspense>
return React.createElement(React.Suspense, {fallback: React.createElement('div', null, 'Now Loading...'), },
React.createElement(MyComponent, {rss:_rss, setrss}),
);
}
//
const MyComponent = props => {
const data = React.use(props.rss);
const rows = data.map((item, index) => {
// <li key={item.id}>{`ほにゃらら`}</li>
return React.createElement('li', {key: item.id}, `${item.title} (${item.price} $)`);
});
const onClick = e => {
props.setrss(getResource(''));
}
// <div>
// <button>Refetch</button>
// <h1>Products</h1>
// <ul>{rows}</ul>
// </div>
return React.createElement('div', null,
React.createElement('button', {onClick}, 'Refetch'),
React.createElement('h1', null, 'Products'),
React.createElement('ul', null, rows)
);
}
//
const root = ReactDOMClient.createRoot(document.getElementById("app"));
root.render(React.createElement(App));
////////////////////////////////////////////////////////////////////////////////
</script>
</body>
</html>
解説
const App = props => {
// fetcher を実行して Promise を取得し、そのタスクをスケジューリングする
// × const [rss, setrss] = React.useState(() => getResource(''));
const [_rss, setrss] = React.useState(rss);
// <Suspense fallback={<div>Now Loading...</div>}>
// <MyComponent rss={rss} setrss={setrss} />
// </Suspense>
return React.createElement(React.Suspense, {fallback: React.createElement('div', null, 'Now Loading...'), },
React.createElement(MyComponent, {rss:_rss, setrss}),
);
}
resource とするリアクティブデータを useState
で管理します。そして、その値や setter を子コンポーネントに渡します。これが refecth が必要な場合の一般的な形です。もちろん useState
以外の Hook でリアクティブデータを管理しても構いません。
なお、const [rss, setrss] = React.useState(() => getResource(''));
と、たとえ lazy 初期化にしたとしてもダメなことはすでに説明しました。
他方、useEffect(() => setrss(getResource(''), []);
とするのは pure ルールには反しませんが、表示のちらつきやそれを防止する策などが必要になるかもしれません。「なぜ私はコンポーネントマウント後にタスクをスケジューリングしようとしているのだろう」と冷静に考えてみましょう。コンポーネントの最初の描画時点や、コンポーネントのマウントを起点としてタスクをスケージューリングする必要性はほとんどの場合なく、多くの場合は、この例のように、モジュールのインポート時やユーザーのアクション時にタスクをスケージューリングすれば十分なはずです。
なお、モジュールのインポート時ではなく、他の js モジュールから「タスクをスケージュール」したい場合は、
let rss;
export function initializer(q) {
rss = getResource(q);
}
のように、イニシャライザを外部に公開する手段もあります。
ただし、このイニシャライザを初期化処理以外の目的(例えば MyComponent の内部から rss を操作する目的)で使用すると pure ルール違反となります。
const MyComponent = props => {
const data = React.use(props.rss);
const rows = data.map((item, index) => {
// <li key={item.id}>{`ほにゃらら`}</li>
return React.createElement('li', {key: item.id}, `${item.title} (${item.price} $)`);
});
const onClick = e => {
props.setrss(getResource(''));
}
// <div>
// <h1>Products</h1>
// <ul>{rows}</ul>
// </div>
return React.createElement('div', null,
React.createElement('button', {onClick}, 'Refetch'),
React.createElement('h1', null, 'Products'),
React.createElement('ul', null, rows)
);
}
基本形と次の点が違うことを特にチェックしてください。
-
use(props.rss)
と、props を経由して取得した Promise を使用しています。 -
onClick
でprops.setrss(getResource(''));
と、props を経由して取得した setter をコールしています。
getResource
が setter よりも先に実行されることはコードから明らかですので、最初の方で約束事とした、render-as-you-fetch がここでもきちんと守られています。なお、仮に setter の前に別の setter が実行されるコードになっていたとしても、setter による再描画は setter を実行するとすぐに行われるわけではなく、少なくとも onClick が完了してから再描画が実行されるため、 render-as-you-fetch は遵守されます。
resouce は Suspense の内側においてもよい
上記の例では resource を Suspense の外側に置きましたが、 React 19 では、resouce を Suspense の内側に置くこともできます(React 19.0, 19.1 で確認済み)。
これは、use(Promise)
による throw Promise
などの影響がその親(ここでは Wrapper
)には及ばないことを意味します。
なお、React のバージョンアップで resource を Suspense の内側に置くことが禁止される可能性もありますが、React 18, React 19RC などから、内側においても動作可能にわざわざ修正してくれたことからすると、99.999% 大丈夫であるとは思います。
const App = props => {
return React.createElement(React.Suspense, {fallback: React.createElement('div', null, 'Now Loading...'), },
React.createElement(Wrapper),
);
}
const Wrapper = props => {
// ★ React 19 では Suspense 内に resource を置いてもよい
const [_rss, setrss] = React.useState(rss);
return React.createElement(MyComponent, {rss:_rss, setrss});
}
const MyComponent = props => {
const data = React.use(props.rss);
const rows = data.map((item, index) => {
return React.createElement('li', {key: item.id}, `${item.title} (${item.price} $)`);
});
const onClick = e => {
props.setrss(getResource(''));
}
return React.createElement('div', null,
React.createElement('button', {onClick}, 'Refetch'),
React.createElement('h1', null, 'Products'),
React.createElement('ul', null, rows)
);
}
以上の基本形と応用形が使いこなせれば、use(resource)
を完璧に使いこなせると思うのですがどうでしょう。
もし他に身に着けるべき応用形があれば是非教えてください。
pure ルールについて
上記のコードには pure ルール違反はありません。他の記事でも何度か pure ルールについては触れましたが、そのまとめ的なものを記載したいと思います。
「ここで変数 v を使うのは pure ルールに反し有罪です!」
という主張への反論が容易ではない場合があります。
JS では const v = プリミティブ
であれば自信をもって
「"const なのに代入してます" というエラーが発生してないので私は無罪です!」
と反論できますが、それ以外の場合、例えば const v = オブジェクト型/配列型
や let v = something
の場合は、
「私は変数 v を10000行あるコード中、どこからも変更していません。よって私は無罪です。」
と立証・主張する必要があります。実際はそんな単純なものではなく、変数が純粋な定数というのは稀で、動的な初期化処理が必要なものであったりしますから
「その変数 v を利用するコンポーネントは C1, C2, C3 ですが、それらのコンポーネントが生存中に v の内容を変更する処理はどこにもありません。よって私は無罪です。」
と立証・主張することになります。
どうやってそのような立証を容易にするか、そこにどこまで手間をかけるか、などについては色々な方法や考えがありますので、そこはそれぞれが好きな方法・考えを採用すればよいと思います。
例えばですが Object.freeze()
を使用すれば立証が容易になるわけですが、「立証が容易になる」だけのものでしかないとも考えられます。後は個人の価値観の問題に他なりません。
もし上記の説明に疑問点などがありましたらまずは上級 SE に相談してください。
モジュールレベルの変数怖いの壁
どうしても上記のような説明に納得できず、モジュールレベルの変数を使うのが怖い方は、Store という概念を利用してはどうでしょうか。
リアクティブデータを返す Hook には、大まかに、「State を管理する Hook」「Context を管理する Hook」「Store を管理する Hook」の3種類があります。
「State を管理する Hook」の代表は useState
Hook で、管轄はコンポーネント内です。
「Context を管理する Hook」の代表は useContext
Hook で、管轄はコンポーネントツリー内です。
そして、「Store を管理する Hook」の代表は useSyncExternalStore
で、管轄はモジュールレベルです。
ですので、どうしてもモジュールレベルの変数怖いの壁を突破できない人は、useSyncExternalStore
を使用すればいいのです。
なお、useReducer
がもっとも基礎的な Hook で、Stete, Context, Store すべてを管理できる Hook という立ち位置にあるものです。実際、useState
は内部で useReducer
を利用しています。そういう意味で、逆に useReducer
を直接使うべき場面はそう多くはありません。
また、use(resource)
は、公式ドキュメントにおいて Hooks
ではなく APIs
に分類されているくらい特殊なもので、use(context)
や、後で紹介する将来的機能 use(store)
というように引数の型に応じて動作が変化することからすると万能型といえそうです。
ところで、React 19 では useContext
の代わりに use(context)
が使えるわけですが、なぜ中の人はそれを追加したのか?中の人が useContext(≪モジュールレベルの変数≫)
を廃止して use(≪モジュールレベルの変数など≫)
に統一したがっている気がする今日この頃です。
useSyncExternalStore
の Cons
さっそく useSyncExternalStore
を使用した例を示したいところですが、 useSyncExternalStore
には Cons があります。
Cons1: ソースがやや複雑化しがち
文字通り Store の存在を前提とする Hook なので、小規模ながら Store を作る必要があります。
そのため、ソースがやや複雑化しがちです。
Cons2: useTransition
と相性が悪い
Sync
と命名されているからなのかそれとは無関係なのか useTransition
Hook と相性が悪いです。もっとも、なぜか Suspense
や useDeferredValue
Hook との相性は悪くないようですが。
以上の点から、「useSyncExternalStore
を使用した例」は後回しにして、上記のような Cons のない、 Jotai
の使用例を示したいと思います。
なお、React の中の人は、Store 型 Hook をあまり好きではない節があります。Store 型は、第三者パーティーはその使いやすさを評価して以前から提供していたのですが、React 公式は長らく State と Context があれば十分でしよ、という態度をとってきました。理由はわかりませんが、React の中の人が関数型プログラミングに固執してるせいなのかな~?真相は如何に?
Jotai とは何者か
Jotai は、Daishi Kato 氏が中心となって作成された React 向けの状態管理ライブラリです。
「Store を管理する Hook」がその機能として搭載されています。
Daishi Kato 氏は useSyncExternalStore
が useTransition
に対応していないことを十分に把握しており、明確な目的を持って useTransition
に対応させておられます。
Jotai がきちんと pure ルールを守っているのかは正直私には分かりません。しかしながら Daishi Kato 氏という、この界隈では有名な、日本の宝といっても過言ではないエンジニアが pure であることを保障してくれているのです。これを使わない手はありません。
「こんな使い方をして pure が保たれているのか自信がないわぁ~」という部分にはとりあえず atom()
などを通しておけば pure であることが保障されるのです!
私のような者がいくら「pure ルールを守った実装です」といっても疑われて当然ですが、あの Daishi Kato 氏による実装ですから安心して使ってよいと考えます。
Jotai は、モジュールレベルの変数怖いの壁を取り除くための希望の星に他ならないのです。
resource 管理に Jotai を利用した例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1, width=device-width" />
</head>
<body>
<div id="app"></div>
<script type="module">
////////////////////////////////////////////////////////////////////////////////
import React from "https://esm.sh/react@19?dev";
import ReactDOM from "https://esm.sh/react-dom@19?dev";
import ReactDOMClient from "https://esm.sh/react-dom@19/client?dev";
import {atom, useAtom} from "https://esm.sh/jotai@2?dev"; // React で ?dev で使う場合はこちらにも ?dev が必要
// fetcher
const getResource = async (q) => {
const response = await fetch(`https://dummyjson.com/products/search?q=${q}`);
// if (!response.ok) throw new Error(`dummyjson API error: ${response.status}`);
const data = await response.json();
return data.products
}
// fetcher を実行して Promise を取得し、そのタスクをスケジューリングする
// atom() には Promise を処理する機能が搭載されてて、それが使われると分かりにくくなるのであえて {value} を通しています
const rss = atom({value: getResource('')});
//
const App = props => {
// <Suspense fallback={<div>Now Loading...</div>}>
// <MyComponent />
// </Suspense>
return React.createElement(React.Suspense, {fallback: React.createElement('div', null, 'Now Loading...'), },
React.createElement(MyComponent),
);
}
//
const MyComponent = props => {
const [_rss, setrss] = useAtom(rss);
const data = React.use(_rss.value);
const rows = data.map((item, index) => {
// <li key={item.id}>{`ほにゃらら`}</li>
return React.createElement('li', {key: item.id}, `${item.title} (${item.price} $)`);
});
const onClick = e => {
setrss({value: getResource('')});
}
// <div>
// <button>Refetch</button>
// <h1>Products</h1>
// <ul>{rows}</ul>
// </div>
return React.createElement('div', null,
React.createElement('button', {onClick}, 'Refetch'),
React.createElement('h1', null, 'Products'),
React.createElement('ul', null, rows)
);
}
//
const root = ReactDOMClient.createRoot(document.getElementById("app"));
root.render(React.createElement(App));
////////////////////////////////////////////////////////////////////////////////
</script>
</body>
</html>
解説
const rss = atom({value: getResource('')});
resource を Jotai の atom として管理します。
atom には、use(Promise)
が当時搭載されていなかった React 18 のための機能として、初期値が Promise 型の場合に内部で use(Promise)
とほぼ同じ処理を行う機能が組み込まれています。
今回その機能を利用してしまうと説明がややこしくなりますので、その機能を避けるために Promise を直接渡さずに {value: Promise}
として渡しております。もっと良い方法があれば教えていただけると幸いです。
const [_rss, setrss] = useAtom(rss);
const data = React.use(_rss.value);
atom から、resource と setter を取得し、use に渡す処理です。非常に単純明快です。props バケツリーレーがなくなりすっきりしました。
const onClick = e => {
rss = setrss({value: getResource('')});
}
当然ですがボタンクリック時の render-as-you-fetch 違反もありません。
なお、 render-as-you-fetch について深く学びたい方は、古い記事が参考になります。
- Render-as-You-Fetch (using Suspense)
-
みんなで一緒に React@19 の use() に慣れておこう
上記「Render-as-You-Fetch (using Suspense)」に付属するTry it on CodeSandbox
のソースを解析して書いた記事です。
resource 管理に useSyncExternalStore
を利用した例
ここまで読んでいただいた上で、それでもなお、useSyncExternalStore
を実戦で使おうという方はおそらくいないでしょうし、難しそうな部分は Store の部分だけですので解説はしません。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1, width=device-width" />
</head>
<body>
<div id="app"></div>
<script type="module">
////////////////////////////////////////////////////////////////////////////////
import React from "https://esm.sh/react@19?dev";
import ReactDOM from "https://esm.sh/react-dom@19?dev";
import ReactDOMClient from "https://esm.sh/react-dom@19/client?dev";
// fetcher
const getResource = async (q) => {
const response = await fetch(`https://dummyjson.com/products/search?q=${q}`);
// if (!response.ok) throw new Error(`dummyjson API error: ${response.status}`);
const data = await response.json();
return data.products
}
// fetcher を実行して Promise を取得し、そのタスクをスケジューリングする
let rss = getResource('');
// Store の機能
const subscribe = notify => {
// 購読の申し込み
setSnapshot = newrss => {
rss = newrss; // snapshot の更新
notify(); // 購読者に通知
};
return () => {}; // // 購読の解除: 今回は何もする必要がない
};
const getSnapshot = () => rss; // snapshot の取得
let setSnapshot; // snapshot の更新
//
const App = props => {
// <Suspense fallback={<div>Now Loading...</div>}>
// <MyComponent />
// </Suspense>
return React.createElement(React.Suspense, {fallback: React.createElement('div', null, 'Now Loading...'), },
React.createElement(MyComponent),
);
}
//
const MyComponent = props => {
const _rss = React.useSyncExternalStore(subscribe, getSnapshot);
const data = React.use(_rss);
const rows = data.map((item, index) => {
// <li key={item.id}>{`ほにゃらら`}</li>
return React.createElement('li', {key: item.id}, `${item.title} (${item.price} $)`);
});
const onClick = e => {
setSnapshot(getResource(''));
}
// <div>
// <button>Refetch</button>
// <h1>Products</h1>
// <ul>{rows}</ul>
// </div>
return React.createElement('div', null,
React.createElement('button', {onClick}, 'Refetch'),
React.createElement('h1', null, 'Products'),
React.createElement('ul', null, rows)
);
}
//
const root = ReactDOMClient.createRoot(document.getElementById("app"));
root.render(React.createElement(App));
////////////////////////////////////////////////////////////////////////////////
</script>
</body>
</html>
最後に
いかがでしたでしょうか。少しでも皆様の参考になれば幸いです。
なお、公式ブログに、use(store)
なるものを実験中であるとのお知らせがあります。
詳細は分かりませんが Store 関係であることは間違いなさそうです。
これがあれば Jotai は不要になるのでしょうか?
さすがに Jotai の全機能がこれに搭載されることはないでしょうから、Jotai が内部で use(store)
を使用する感じになるだけの気がします。atom が useTransition
に見事に対応されていることはお話しましたが、ティアリング的なものを避けるためにはどうしても use(store)
のようなものを利用せざるを得ないからです。
もしかしたら、Jotai を、「モジュールレベルの変数怖いの壁を突破するもの」くらいにしか使わない場合は、 use(store)
で十分になるのかもしれません。もしそうなると、 use(store)
は平気だが use(promise)
は怖いという不思議なことになリますね。
何れにせよ、use(store)
早く使ってみたいですね。