빠른 시작
설치
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, custom 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 조합을 읽어보세요.