@ comwit/ 状态
反应状态为你和你的经纪人.
React and Next.js的州管理层,公布于 @comwit/state保持客户状态,
服务器查询,和一个域中的动作,然后通过一个打入的钩子使用.
用于1 000多个项目 (原始内容存档于2013-10-12). comwit.io,一个克洛德代码动力的氛围编码平台 从韩国。 发起人: 伯尔。 。 。 .
为什么是傻瓜?
- 一域一钩. 定义一个模型,添加动作,并将其与
create()。 。 。 . - 客户端状态和服务器数据在一起. 反应值、缓存查询和服务器水合 共享同一个域.
- 行动内部有直接突变. 以普通 JavaScript 更新状态; 组件订阅 通过选择器.
- 为编码代理所建. 紧凑
llms.txt盖被子, 域结构,以及核心 API.
安装
npm install @comwit/state
支助 反应 18和19。对于下一个.js,跟着 应用路由器设置。 。 。 .
与人工智能特工合作
给Claude代码或你的编码代理这个提示:
Set up @comwit/state in this project using https://library.comwit.io/state/llms.txt.
Follow its domain structure and state management patterns.
快开始
一个文件中的完整计数器 :
'use client'
import { action, ComwitProvider, create, model } from '@comwit/state'
type CounterState = { count: number }
type CounterActions = { increment(): void; reset(): void }
const counter = model<CounterState>({ count: 0 })
const counterActions = action<CounterActions>(({ state }) => {
const m = state(counter)
return {
increment() {
m.count += 1
},
reset() {
m.count = 0
},
}
})
const useCounter = create<CounterState, CounterActions>(counter, {
actions: [counterActions],
})
function Counter() {
const { count, actions } = useCounter((s) => ({ count: s.count, actions: s.actions }))
return (
<div>
<p>Count: {count}</p>
<button onClick={actions.increment}>Add one</button>
<button onClick={actions.reset}>Reset</button>
</div>
)
}
export default function App() {
return (
<ComwitProvider>
<Counter />
</ComwitProvider>
)
}
每个 ComwitProvider 拥有它的状态,模型仅在访问时初始化. 随着应用程序的增长,
拆分模型、动作和钩入 域文件夹。 。 。 .
超越客户端状态
| 你需要的东西 | API / 指南 |
|---|---|
| 带有 SSR 和 Suspense 的渲染服务器数据 | .suspend(arg) |
| 在浏览器中获取并缓存数据 | query() + 键 .load(arg) |
无安全部门改革替代品 useServerInsertedHTML | useDomain.hydrate(...) |
| 在不请求API的情况下恢复精确的索引DB视图 | local() + 键 .restore(arg) |
| 保留索引DB视图并重新验证服务器 | local.query() 编号 : local.infinite() |
| 坚持浏览器自有偏好 | persist() |
| 来自状态的衍生值 | computed() |
| 撤销和重做状态更改 | $history |
| 在动作中添加重试、 跳出或共享错误处理 | 动作装饰器 |
查询和下一个.js
读取服务器数据 .suspend(arg) 在客户部分中,在内部 <Suspense>数字 :
'use client'
function ProductDetail({ id }: { id: string }) {
const product = useProduct((s) => s.detail.suspend(id))
return product.data ? <h1>{product.data.title}</h1> : null
}
<Suspense fallback={<ProductSkeleton />}>
<ProductDetail id={id} />
</Suspense>
下一个通过 js useServerInsertedHTML 给提供商一次。 服务器渲染运行 queryFn 财务报告和审定财务报表
将结果流入浏览器缓存中,所以水合使得它没有第二次请求. 那边
没有服务器组件获取,也没有路由适配器.
'use client'
import { useServerInsertedHTML } from 'next/navigation'
<ComwitProvider useServerInsertedHTML={useServerInsertedHTML}>{children}</ComwitProvider>
queryFn 运行在服务器和浏览器上,所以给一个具有绝对URL的API打电话,并返回JSON数据.
使用 .load(arg) 当组件成为自己的装入状态时。 报告 isLoading 期间
承诺后提出并开始请求,在安全部门改革期间从未:
const list = useProduct((s) => s.list.load({ page }))
if (list.isLoading) return <Skeleton />
没有读取查询状态 .load() 或者说 .suspend() 不启动请求.
在一个提供者没有 useServerInsertedHTML (或等同),服务器响应
无法到达客户端缓存,所以客户端导航再次请求它. 在服务器上获取
将结果传递给 useProduct.hydrate({ detail: { arg: id, data: initialProduct } }) 在此之前
域钩读取. 下一个J不需要这个 见 Next.js 向导。 。 。 .
从列表项目中获取细节
从列表中打开的细节可以同时从列表项目中画出,并在后面填入剩下的
承诺 宣布种子形状为 query() 从模型中推导出来
与 seed; 不变 .suspend(id) 然后直接访问(中止、流出)和适用
导航( 涂抹种子, 背景升级):
const catalog = model({
list: query<ProductSummary[]>({ initialData: [], queryFn: api.product.list }),
detail: query<ProductDetail | null, string, ProductSummary>({
initialData: null,
queryFn: (id) => api.product.find(id),
seed: (id, { state }) => state.list.data.find((item) => item.id === id),
}),
})
const detail = useCatalog((s) => s.detail.suspend(id))
if (!detail.data) return <ProductNotFound />
if (detail.isPartial) return <Product summary={detail.data} /> // detail-only fields are optional
return <Product summary={detail.data} reviews={detail.data.reviews} />
其结果是,工会受到歧视 isPartial当钥匙显示它的种子时, data 实值
ProductSummary & Partial<ProductDetail>, (中文(简体) ), isPartial 实值 true, (中文(简体) ), isLoading编号 :isSuccess 为:
false,以及 isFetching 实值 true 直到升级结束 完全结果(isPartial: false(中文(简体) )
已经 data: ProductDetail | null。没有种子的查询保留其现有的结果类型.
规则:
seed在渲染过程中运行, 所以它必须是纯的: 读state(模型本身的状态,只读) 快照,别写 返回时null编号 :undefined当模型没有钥匙时.state输入any因为query()在模型存在前宣布 上下文参数为QuerySeedContext<CatalogState>输入它.TSeed可能只命名全部值的字段,类型相同;额外密钥是类型错误.- 带着种子,
.suspend(id)不暂停和.load(id)不报告isLoading; 两者 画出种子并在承诺后开始升级。 既无种子又无缓存,.suspend(id)和以前一样暂停和流出. - 一个完全的结果是,钥匙总是胜过它的种子,它永远不会降级,
.set(), (中文(简体) ),hydrate()和流式服务器结果都是完整结果. staleTime在键为部分时被忽略:升级总是运行.- 升级被拒绝:种子保留,
isError: true, (中文(简体) ),isPartial: true(单位:千美元)refetch()重审). 升级到null数字 :data === null, (中文(简体) ),isPartial: false,所以一个!data分支生成 还没找到. - 种子为一键. 更改参数从不显示另一个密钥的种子, 并挂载多个 读者每人画自己的钥匙.
local.query() 一样 seed 通过同一第三种通用。 当地资源与
.load(id) (单位:千美元)local() 与 .restore(id)(三); .suspend() 无法在其上找到, 因为类型错误
并且作为无型号呼叫器的抛出错误,因为暂停会绕过持久恢复.
带着种子, .load(id) 以同步方式绘出isPartial: true永远 isLoading页:1
为密钥所存储的索引DB视图,然后将其替换为一个完整的值(isPartial: false一样
hydrate()),网络重新验证每 staleTime。没有存储的行,种子将保留
直到网络结果。 此提供者中已经恢复的密钥永远不会再次显示其种子 .
阅读另一个模型(实验):种子背景也提供 read(model) 对于一个种子
源居于另一个域的模式 :
seed: (slug, { state, read }) =>
state.list.data.find((p) => p.slug === slug) ??
read(lounge)?.overview.data?.blogs.find((b) => b.slug === slug)
建议的设计保留每个资源一个模型(一个需要多个资源调用的页面)
所以,一个细节的种子来源 通常生活在自己的模型和 state 实值
够了. read 运行在渲染的时间, 并且从未初始化, 挂载或观察它的模式
给定: 除非有其他东西在提供者中注册该模型,否则返回 undefined 财务报告和审定财务报表
键入或暂停。 它返回相同的只读快照 state 是( 无)
.load(),没有取来,写出掷出。 种子不订阅:
读取密钥时的当前快照, 因此以后对其它模型的更改不会重播 a
键已绘制.
seed 与 placeholderData: 占位符保留 另一个键 完全可见结果
新的密钥负载; 种子是 同样的钥匙 部分价值。 无限和实时资源
不要取一个种子.
缓慢的初始装入
设定 slowLoadingMs: 500 打开 query() 或者说 local.query() 读取 isSlowLoading 通过通常
查询状态。 现有的加载旗帜,请求,缓存新鲜度,以及数据发送都保持它们的时间安排.
新旗帜为快速完成和可用缓存数据而保持不实,成为持续状态的真面目
初始负载,并立即清除成功/错误。 不需要组件、效果或请求密钥.
见 查询指南 为了渲染,
SSR,重试,清理,以及所支持的查询变体. 这是查询声明选项, 不是
模型/提供者默认或独立 local() 选项.
URL 状态
searchParam() 在模型中直接声明输入的 URL 备份字段。 读他们通过相同的
域名勾通,并通过动作更新它们.
npm install @comwit/[email protected]
见 URL 状态参考 对于分析,默认, 浏览器历史和服务器渲染.
文档
开始于 图书馆. comwit.io。 。 。 .
从 comwit
自 v 2. . 0. 0 起, 包被命名为 @comwit/state。替换依赖性并更新进口:
- import { model, action } from 'comwit'
+ import { model, action } from '@comwit/state'
重命名没有更改 API 。 堕落者 comwit 包再导出 @comwit/state 页:1
兼容性。 见 v 2.0.0 移徙说明。 。 。 .