クイックスタート
インストール
npm install @violetflux/kerros
pnpm add @violetflux/kerros
yarn add @violetflux/kerros
bun add @violetflux/kerros
Kerros は npm、pnpm、Yarn、Bun で利用でき、React 17、18、19 をサポートします。
Store を作成する
任意の custom Hook を createStore でラップすると、コンポーネント間で状態を共有できます。
import { createStore } from '@violetflux/kerros'
import { useState } from 'react'
interface Task {
id: string
title: string
}
function useTaskStoreValue() {
const [tasks, setTasks] = useState<Task[]>([])
const addTask = (task: Task) => {
setTasks(v => [...v, task])
}
const finishTask = (taskId: string) => {
setTasks(v => v.filter(task => task.id !== taskId))
}
return { tasks, addTask, finishTask }
}
export const [useTask, TaskProvider] = createStore(useTaskStoreValue)
createStore は、コンポーネントが使う Hook と対応する Provider を返します。Store の中では useState、useReducer、Context、SDK Hook、独自 Hook をそのまま使えます。
initializer は useTaskStoreValue のようなトップレベルの名前付き Hook にしてください。匿名 initializer も動作しますが、React Compiler の infer モードでは Hook として自動コンパイルされません。
Provider を配置する
useTask は TaskProvider の子孫でのみ呼び出せます。
function App() {
return (
<TaskProvider>
<Header />
<TaskList />
</TaskProvider>
)
}
Provider の外で呼び出すと、Kerros は明確なエラーを投げます。
コンポーネントで Store を使う
selector から、コンポーネントが使うフィールドだけを返します。
function TaskList() {
const { tasks, finishTask } = useTask(s => ({
tasks: s.tasks,
finishTask: s.finishTask,
}))
return (
<ul>
{tasks.map(task => (
<li key={task.id}>
{task.title}
<button onClick={() => finishTask(task.id)}>完了</button>
</li>
))}
</ul>
)
}
Kerros は selector オブジェクトのトップレベルを浅く比較します。選択したフィールドが同じなら、Store の他の更新で TaskList は再レンダーされません。selector はインラインでよく、useCallback は不要です。
複数の Store インスタンス
Provider を配置するたびに、独立した Store が作られます。
<TaskProvider>
<h2>個人タスク</h2>
<TaskList />
</TaskProvider>
<TaskProvider>
<h2>チームタスク</h2>
<TaskList />
</TaskProvider>
各 TaskList は最も近い Provider を自動的に読み、データは互いに影響しません。
Store 間の依存
ある Store から別の Store を直接呼び出せます。
function useTaskStoreValue() {
const { user } = useAccount(s => ({ user: s.user }))
const [tasks, setTasks] = useState<Task[]>([])
const addTask = (title: string) => {
if (!user)
return
setTasks(v => [...v, {
id: crypto.randomUUID(),
title,
assigneeId: user.id,
}])
}
return { tasks, addTask }
}
export const [useTask, TaskProvider] = createStore(useTaskStoreValue)
依存される Provider を外側に配置し、依存関係を一方向に保ちます。
<AccountProvider>
<TaskProvider>
<App />
</TaskProvider>
</AccountProvider>
Provider に props を渡す
Provider の props は Store Hook の引数になります。
function useCounterStoreValue({ initialCount }: { initialCount: number }) {
const [count, setCount] = useState(initialCount)
return { count, setCount }
}
const [useCounter, CounterProvider] = createStore(useCounterStoreValue)
<CounterProvider initialCount={42}>
<Counter />
</CounterProvider>
試してみる
このカウンターも同じ createStore + Provider + selector API を使っています。
次は Selector と Store の合成 を参照してください。