@violetflux/kerros
v0.3.4
Published
Hook-native state sharing for React with automatic access tracking and focused selectors.
Downloads
1,987
Maintainers
Readme
Kerros 是一个在 React 组件间共享状态的轻量方案。
你怎么写 custom Hook,就可以怎么写 Store。只有当局部状态需要被多个组件使用时,再交给 createStore,用 Provider 决定共享范围;组件直接解构需要的数据,Kerros 默认自动追踪实际访问的属性。
[!TIP] 使用 Coding Agent 安装:复制下面这句话并粘贴给你的 Coding Agent,它会同时安装依赖和当前项目的 Skill:
使用当前项目的包管理器安装 @violetflux/kerros,然后运行 npx skills add violetflux/kerros --skill kerros --agent '*' -y,为当前项目中所有兼容的 Coding Agent 安装 Kerros Skill。快速上手
创建 Store
任意 custom Hook 都可以变成 Kerros Store:
import { createStore } from '@violetflux/kerros'
import { useState } from 'react'
interface Task {
id: string
title: string
}
function useTaskModel() {
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, getTask] = createStore(useTaskModel)createStore 返回组件 Hook、对应的 Provider,以及在 React 外命令式读取已提交实例的 getter。
Store 仍然是普通 React Hook,可以继续使用 useState、useReducer、Context、SDK Hook 或其他 custom Hook。
请把 initializer 写成 useTaskModel 这类同文件顶层命名 Hook。匿名 initializer 在运行时仍然可用,但 React Compiler 的 infer 模式不会自动把它识别并编译为 Hook。
挂载 Provider
只有 TaskProvider 的子节点可以调用 useTask:
function App() {
return (
<TaskProvider>
<Header />
<TaskList />
</TaskProvider>
)
}使用 Store
直接读取 Store。Kerros 会自动追踪组件渲染期间访问的属性:
function TaskList() {
const { tasks, finishTask } = useTask()
return (
<ul>
{tasks.map(task => (
<li key={task.id}>
{task.title}
<button onClick={() => finishTask(task.id)}>完成</button>
</li>
))}
</ul>
)
}没有读取的字段发生变化时,TaskList 不会重渲染。自动追踪支持对象、数组和深层属性访问,不会对完整 Store 做深比较。
安装
| 包管理器 | 命令 |
| --- | --- |
| npm | npm install @violetflux/kerros |
| pnpm | pnpm add @violetflux/kerros |
| Yarn | yarn add @violetflux/kerros |
| Bun | bun add @violetflux/kerros |
支持 React 17、React 18 和 React 19。
为什么要用 Kerros?
- 几乎没有学习成本:直接复用已有的 React 知识,你怎么写 custom Hook,就可以怎么写 Store
- 为灵活重构而设计:Store 和组件使用同一套 Hook API,可以近乎零成本地把组件局部状态转换成组件间共享状态
- 同时支持局部状态和全局状态:Provider 决定 Store 的作用域,在灵活和简单之间取得平衡
- 解决 Context 的重复渲染问题:Context 只传递稳定容器,组件观察到的值不变时不会重渲染
- 优秀的 TypeScript 支持:Store 和 selector 类型自动推断,不需要重复声明
从状态管理到状态共享
Redux、Zustand、Recoil 这些状态管理库当然也能解决数据共享问题,但它们最核心的能力仍然是组织数据、操作数据和约束数据流,因此它们应该被称为“状态管理”工具。
Kerros 想解决的问题更小,也更直接。它不发明新的数据结构,不规定异步和数据流应该怎么写,只聚焦一个痛点:如何在多个 React 组件间共享一段 Hook 状态。
层层传递 value、onChange 会逐渐破坏组件边界;粗暴地把数据全部塞进一个全局 Store,也不会自动让应用获得更好的扩展性和可维护性。
直接用 React Context 共享变化频繁的状态也会带来重复渲染:Context value 每次变化,所有消费者都会更新。Kerros 保留 Provider 的作用域和多实例能力,但 Context 只传递稳定容器;自动追踪根据渲染期间的读取建立订阅,无关 Store 更新不会触发组件重渲染。
Kerros 简单、轻量、可靠。先把状态写成普通 Hook,需要共享时再交给 createStore;Provider 决定状态共享到哪里,自动追踪决定每个组件订阅什么。
三种订阅模式
不传 selector 是默认用法,也是多数场景的起点:
const { count, setCount } = useCounter()useStore():自动追踪渲染期间读取的对象、数组和深层属性。useStore(selector):用于高级派生值和经过测量的性能热点;Kerros 用Object.is浅比较 selector 返回对象的顶层字段。createStore(model, { tracking: false })或bindStore({ tracking: false }):关闭自动追踪,无 selector 读取改为完整 Store 顶层浅比较。
基础类型快照使用 Object.is。Map、Set、类实例及其他非普通对象按整体引用处理。Store 和 External Store 快照必须保持不可变:每次可观察变化都发布新引用。
React Element 和 Portal 会自动作为原子值处理。React 17、18、19 的标准 useRef()、createRef() 容器可以直接返回。只有第三方对象不能接受 Proxy,或者必须保留严格身份时才使用 ref(value);原子值的内部原地修改不是响应式更新。
无 selector 的结果是当前组件的只读追踪快照,可以直接解构、保存在渲染局部变量中,或传给同步渲染的子组件继续读取。不要修改快照,也不要把它保存到 state、ref、模块变量或长期缓存后当作实时状态源;展开、rest 解构、枚举和序列化会形成宽泛订阅。响应式 Effect 应在渲染期间读取值并声明正确依赖;只有不参与渲染、需要执行时读取最新状态的命令式逻辑才使用 useInstance()。也不要把 Effect Event 暴露成公共 Store action。
多个实例
每个 TaskProvider 都拥有独立状态:
<TaskProvider>
<h2>个人任务</h2>
<TaskList />
</TaskProvider>
<TaskProvider>
<h2>团队任务</h2>
<TaskList />
</TaskProvider>每个 TaskList 会自动读取离自己最近的 Provider。
Store 之间的依赖
一个 Store 可以直接调用另一个 Store。例如任务 Store 读取当前账户:
function useTaskModel() {
const { user } = useAccount()
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(useTaskModel)按依赖顺序挂 Provider,并保持单向依赖:
<AccountProvider>
<TaskProvider>
<App />
</TaskProvider>
</AccountProvider>Provider props
Provider props 会传给 Store Hook:
interface CounterProps {
initialCount: number
}
function useCounterModel({ initialCount }: CounterProps) {
const [count, setCount] = useState(initialCount)
return { count, setCount }
}
const [useCounter, CounterProvider] = createStore(useCounterModel)<CounterProvider initialCount={42}>
<Counter />
</CounterProvider>API
createStore(默认用法)
function createStore<TStore, TProps = Record<never, never>>(
useModel: (props: TProps) => TStore,
options?: { tracking?: boolean },
): readonly [StoreHook<TStore>, StoreProvider<TProps>, StoreGetter<TStore>]useModel必须遵守 Hooks 规则- 除
children外的 Provider props 会传给useModel - Provider 接受可选的
scope?: string | number | symbol,用于命令式查找 - Store Hook 可不传参数使用自动追踪,也可传入返回对象的 selector
- getter 读取最后挂载且已提交的 Provider,或 scope 精确匹配的最后一个实例;它不会订阅更新
- 在对应 Provider 外调用会抛出明确错误
- 支持 Strict Mode、服务端渲染和 Provider 多实例
ref(身份逃生口)
function ref<T extends object>(value: T): T把对象标记为原子值并返回完全相同的身份。标准 React ref 不需要这个辅助函数。
高级用法:绑定已有 External Store
绝大多数应用只需要 createStore。只有当某个库或 SDK 已经在 React 外持有权威状态,并提供稳定的 getSnapshot 和 subscribe 函数时,才使用 bindStore。
没有这个 API 时,集成层只能重复实现 Context 和细粒度订阅,或者把 External Store 快照复制进第二个 Store。bindStore 提供 Provider 作用域、无 selector 自动追踪和显式 selector,原 Store 仍是唯一状态所有者。
const [useStream, StreamBindingProvider] = bindStore<Stream>('Stream')
<StreamBindingProvider store={stream}>
<App />
</StreamBindingProvider>Provider 的 Context 只保存原 Store 实例,组件直接订阅它;Kerros 不复制快照,也不增加中间发布层。创建 Store 的组件继续负责生命周期和命令式访问。
一句话理解:bindStore 是 External Store 的 React 适配器,不是状态同步器。状态仍只存在于原 Store 中,React 在收到订阅通知后通过 getSnapshot 读取它。
如果状态来自 useState、useReducer、SDK Hook 或其他 custom Hook,继续使用 createStore。
React 17 使用官方 use-sync-external-store shim;React 18 和 19 可用时优先使用 React 原生实现。React Compiler 不是必需项。
ESLint 防护规则
建议安装轻量插件,检查当前文件内的 Kerros 约束:
npm install --save-dev @violetflux/eslint-plugin-kerros @typescript-eslint/parserimport kerros from '@violetflux/eslint-plugin-kerros'
export default [kerros.configs.recommended]recommended 是插件唯一的预设,不启用 TypeScript projectService。它识别直接导入的 createStore、bindStore、本地别名和 namespace 导入,并检查同一文件内生成的 Store 绑定与 Hook 调用。跨文件转导出、包装函数和从其他文件导入的 Store Hook 有意留在轻量边界之外。请参考真实 ESLint 压测。
预设包含六条规则,覆盖工厂作用域、Model 与绑定命名、selector 参数名、返回完整 Store,以及对当前文件无 selector 快照的宽泛访问。插件优先保证内存有界和诊断稳定,不追踪跨文件类型身份。
维护者还需要分别为 @violetflux/kerros 和 @violetflux/eslint-plugin-kerros 配置 npm Trusted Publisher。这是唯一的仓库外发布步骤;仓库内工作流会先检查并发布运行库,再发布插件。
