用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/RunnerQuan/SAFE-Agent --skill jotai-react-mastery命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | jotai-react-mastery |
| description | JotaiとReact Suspense/Transitionを組み合わせたモダンな状態管理・非同期処理のベストプラクティス集。uhyo氏の「jotaiによるReact再入門」に基づく。 |
| allowed-tools | ["Shell","Read","Write","Edit","Glob","Grep"] |
Jotaiを用いたReactアプリケーション設計、特にSuspense、Concurrent features(Transition)、非同期処理、エラーハンドリングに関するベストプラクティスを提供するスキルです。 「jotaiによるReact再入門」の内容をベースに、宣言的UIの原則に従った実装パターンを提示します。
useTransition を用いたUX改善(ちらつき防止、ペンディング表示)jotai-eager を用いたパフォーマンス最適化atomでステートを定義し、useAtomで利用する。useStateの役割を分割・拡張する。Suspenseを正しく動作させるため、Promiseはコンポーネント内(useEffectやuseMemo内)ではなく、**コンポーネントの外(Atom)**で管理する。
// コンポーネント外でPromiseを保持する
const userAtom = atom(async () => {
const user = await fetchUser();
return user;
});
// コンポーネント内
const UserProfile = () => {
// atomの値がPromiseの場合、解決するまで自動的にサスペンドする
const user = useAtomValue(userAtom);
return <div>{user.name}</div>;
};
IDごとのデータ取得には以下の2パターンを使い分ける。
パラメータ依存atom(単一のパラメータのみ扱う場合)
const userIdAtom = atom<string | null>(null);
const userAtom = atom(async (get) => {
const id = get(userIdAtom);
if (!id) return null;
return fetchUser(id);
});
Atom Family(複数のパラメータを同時に扱う、キャッシュが必要な場合)
import { atomFamily } from 'jotai/utils';
const userAtomFamily = atomFamily((id: string) =>
atom(async () => fetchUser(id))
);
const UserProfile = ({ id }) => {
const user = useAtomValue(userAtomFamily(id));
// ...
};
データの再取得(Refetch)は「手続き的な再実行」ではなく、「UIバージョン(キー)の更新によるステートの再評価」として実装する。これは「データ取得もUIの計算の一部」という宣言的UIの思想に基づく。
import { atom, type Getter } from "jotai";
function createReloadableAtom<T>(getter: (get: Getter) => T) {
const refetchKeyAtom = atom(0);
return atom(
(get) => {
get(refetchKeyAtom); // 依存を作成
return getter(get);
},
(get, set) => {
// バージョンを更新して再評価をトリガー
set(refetchKeyAtom, (c) => c + 1);
}
);
}
// 使用例
const userListAtom = createReloadableAtom(async () => fetchUsers());
// コンポーネント内での使用
const UserList = () => {
const users = useAtomValue(userListAtom);
const reload = useSetAtom(userListAtom); // 実行すると再取得
// ...
};
Suspenseによるフォールバック表示(ローディング)のちらつきを防ぎ、「古いUI」を維持しつつ裏で読み込む。
useTransition / startTransition: ステート更新をラップして「優先度の低い更新」とする。isPending を利用して、古いUIが表示されている間に「読み込み中...」などのフィードバックを即座に返す。const [isPending, startTransition] = useTransition();
const handleChange = (nextId) => {
startTransition(() => {
setUserId(nextId); // この更新によるサスペンドはフォールバックを表示せず、古いUIを維持する
});
};
// UI側: isPendingを用いて応答性を確保
<div style={{ opacity: isPending ? 0.5 : 1 }}>
<Suspense fallback={<Spinner />}>
<UserProfile id={userId} />
</Suspense>
</div>
Suspense に key を与えるか、条件付きレンダリングで新しい Suspense インスタンスを生成することで、強制的にフォールバックを表示させる。初期ロード時は非同期(Promise)だが、キャッシュがある場合は同期的に値を返したい場合に jotai-eager を使用する。無駄なサスペンド(一瞬のLoading表示)を防ぐ。
import { eagerAtom } from 'jotai-eager';
// 内部状態がnull(初期状態)のときだけ非同期、それ以外は同期
const valueAtom = eagerAtom((get) => {
const internalVal = get(internalAtom);
if (internalVal !== null) {
return internalVal; // 同期的に返す
}
// 非同期読み込み
const data = get(asyncDataAtom);
return data; // Promiseになる可能性がある
});
eagerAtom は、getでPromiseを取得したときのみ自身もPromiseを返し、それ以外は値をそのまま返す(同期)。
非同期atomのPromiseがrejectされた場合、コンポーネントでエラーがスローされる。
react-error-boundary の利用を推奨。resetErrorBoundary) と、Atomの再読み込み (reloadAtom) をトランジション内で同時に行う。const UserListErrorFallback = ({ error, resetErrorBoundary }) => {
const reloadUserList = useSetAtom(userListAtom); // createReloadableAtomで作成したもの
const handleRetry = () => {
startTransition(() => {
resetErrorBoundary(); // Error Boundaryの状態リセット
reloadUserList(); // Atomの再評価トリガー
});
};
return (
<div>
<p>Error: {error.message}</p>
<button onClick={handleRetry}>Retry</button>
</div>
);
};