docs(record): add bilingual usage guides
This commit is contained in:
@@ -75,6 +75,14 @@ const packageDefinitions: readonly PackageDefinition[] = [
|
||||
us: "Manage typed, extensible application preferences with SSR snapshots, persistence callbacks, and composable effects.",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "record",
|
||||
label: "Record",
|
||||
description: {
|
||||
zh: "通过可组合上下文、类型安全字段路径和记录迭代器构建数据驱动界面。",
|
||||
us: "Build data-driven interfaces with composable contexts, typed field paths, and record iteration.",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "search",
|
||||
label: "Search",
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: Nested contexts and stable identity
|
||||
description: Compose local record contexts and select stable React keys for record lists.
|
||||
order: 20
|
||||
toc:
|
||||
- id: nested-records
|
||||
title: Nested records
|
||||
- id: optional-provider
|
||||
title: Optional provider
|
||||
- id: stable-keys
|
||||
title: Stable identity
|
||||
---
|
||||
|
||||
## Nested records {#nested-records}
|
||||
|
||||
Providers can be nested. Fields inside the inner provider read the local record, and the outer record becomes active again outside that boundary.
|
||||
|
||||
```tsx
|
||||
<RecordProvider value={order}>
|
||||
<OrderNumber />
|
||||
|
||||
<RecordProvider value={order.customer}>
|
||||
<CustomerName />
|
||||
</RecordProvider>
|
||||
</RecordProvider>
|
||||
```
|
||||
|
||||
Do not use record context as a mutable store. Pass a new record reference when new data arrives, and keep editable values in a form or another state layer.
|
||||
|
||||
## Optional provider {#optional-provider}
|
||||
|
||||
`OptionalRecordProvider` creates a new boundary only when `value` is neither `undefined` nor `null`. A nullish value preserves the nearest parent record, while `0`, `false`, and an empty string still create a new boundary. This is useful for components that accept a prop but can also compose into an existing record view.
|
||||
|
||||
```tsx
|
||||
function ProductCard({ product }: { product?: Product }) {
|
||||
return (
|
||||
<OptionalRecordProvider value={product}>
|
||||
<ProductTitle />
|
||||
<ProductPrice />
|
||||
</OptionalRecordProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Stable identity {#stable-keys}
|
||||
|
||||
`RecordsIterator` uses string, number, and bigint records directly as keys. For object records it reads `id` by default. Other values and objects without an `id` can provide a `recordKey` callback; objects can also provide a field name. It falls back to the array index only when no valid key is available.
|
||||
|
||||
```tsx
|
||||
<RecordsIterator
|
||||
data={locales}
|
||||
iterator={(locale) => <LocaleRow />}
|
||||
recordKey="code"
|
||||
/>
|
||||
|
||||
<RecordsIterator
|
||||
data={revisions}
|
||||
recordKey={(revision) => `${revision.documentId}:${revision.version}`}
|
||||
>
|
||||
<RevisionRow />
|
||||
</RecordsIterator>
|
||||
```
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: Record list example
|
||||
description: Render a contextual record list with reusable field components.
|
||||
order: 30
|
||||
toc:
|
||||
- id: define-field-components
|
||||
title: Define field components
|
||||
- id: render-the-list
|
||||
title: Render the list
|
||||
- id: iterator-callback
|
||||
title: Use an iterator callback
|
||||
- id: add-separators
|
||||
title: Add separators
|
||||
---
|
||||
|
||||
## Define field components {#define-field-components}
|
||||
|
||||
Field components depend only on record context, so the same fields work in detail pages, lists, and nested records.
|
||||
|
||||
```tsx
|
||||
function ProductName() {
|
||||
const name = useFieldValue<Product, "name">({ source: "name" })
|
||||
return <strong>{name}</strong>
|
||||
}
|
||||
|
||||
function ProductPrice() {
|
||||
const price = useFieldValue<Product, "price">({ source: "price" })
|
||||
return <span>${price}</span>
|
||||
}
|
||||
```
|
||||
|
||||
## Render the list {#render-the-list}
|
||||
|
||||
Pass shared children to the iterator. It provides a fresh context for every record.
|
||||
|
||||
```tsx
|
||||
<ul>
|
||||
<RecordsIterator data={products}>
|
||||
<li>
|
||||
<ProductName />
|
||||
<ProductPrice />
|
||||
</li>
|
||||
</RecordsIterator>
|
||||
</ul>
|
||||
```
|
||||
|
||||
The iterator returns `null` when `data` is empty or undefined, or when neither children nor iterator is provided. The surrounding data view remains responsible for loading, error, and empty states.
|
||||
|
||||
## Use an iterator callback {#iterator-callback}
|
||||
|
||||
Simple views can use the callback argument directly. The callback is still rendered inside the matching record context, so nested components can keep using the hooks.
|
||||
|
||||
```tsx
|
||||
<RecordsIterator
|
||||
data={products}
|
||||
iterator={(product, index) => (
|
||||
<article>
|
||||
<span>#{index + 1}</span>
|
||||
<ProductName />
|
||||
<button onClick={() => addToCart(product.id)}>Add</button>
|
||||
</article>
|
||||
)}
|
||||
/>
|
||||
```
|
||||
|
||||
## Add separators {#add-separators}
|
||||
|
||||
Use `separator` to insert content between adjacent records. It renders neither before the first record nor after the last one. Its argument is the index of the record following the separator, and the separator remains outside that record's `RecordProvider`.
|
||||
|
||||
```tsx
|
||||
<RecordsIterator
|
||||
data={products}
|
||||
separator={(index) => <hr aria-label={`Separator ${index}`} />}
|
||||
>
|
||||
<ProductCard />
|
||||
</RecordsIterator>
|
||||
```
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: Provide and read records
|
||||
description: Build record-aware components with RecordProvider, useRecord, and useFieldValue.
|
||||
order: 10
|
||||
toc:
|
||||
- id: provide-a-record
|
||||
title: Provide a record
|
||||
- id: read-a-record
|
||||
title: Read a record
|
||||
- id: read-a-field
|
||||
title: Read a field
|
||||
---
|
||||
|
||||
## Provide a record {#provide-a-record}
|
||||
|
||||
Mount the provider at the boundary that owns the data. `RecordProvider` accepts a present, non-nullish record. Use `OptionalRecordProvider` when a query result may be absent.
|
||||
|
||||
```tsx
|
||||
import { RecordProvider } from "@workspace/record"
|
||||
|
||||
function ProductScreen({ product }: { product: Product }) {
|
||||
return (
|
||||
<RecordProvider value={product}>
|
||||
<ProductDetails />
|
||||
</RecordProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
import { OptionalRecordProvider } from "@workspace/record"
|
||||
|
||||
;<OptionalRecordProvider value={query.data}>
|
||||
<ProductDetails />
|
||||
</OptionalRecordProvider>
|
||||
```
|
||||
|
||||
## Read a record {#read-a-record}
|
||||
|
||||
`useRecord` returns `undefined` when there is no provider or data has not loaded yet.
|
||||
|
||||
```tsx
|
||||
function ProductDetails() {
|
||||
const product = useRecord<Product>()
|
||||
if (!product) return <p>Loading…</p>
|
||||
|
||||
return <h2>{product.name}</h2>
|
||||
}
|
||||
```
|
||||
|
||||
A record-aware component can also accept an optional `record` prop. The explicit record takes precedence over context, making isolated previews and tests straightforward.
|
||||
|
||||
```tsx
|
||||
function ProductTitle({ record }: { record?: Product }) {
|
||||
const product = useRecord({ record })
|
||||
return product ? <h2>{product.name}</h2> : null
|
||||
}
|
||||
```
|
||||
|
||||
## Read a field {#read-a-field}
|
||||
|
||||
`useFieldValue` supports direct properties, nested objects, and numeric array segments. Paths use dot notation.
|
||||
|
||||
```tsx
|
||||
const city = useFieldValue<Customer, "address.city">({
|
||||
source: "address.city",
|
||||
defaultValue: "Unknown",
|
||||
})
|
||||
|
||||
const firstSku = useFieldValue<Order, "items.0.sku">({
|
||||
source: "items.0.sku",
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Learn how the Record package exposes the current record in data-driven interfaces.
|
||||
order: 1
|
||||
toc:
|
||||
- id: package-role
|
||||
title: Package role
|
||||
- id: capabilities
|
||||
title: Capabilities
|
||||
- id: design-boundary
|
||||
title: Design boundary
|
||||
---
|
||||
|
||||
## Package role {#package-role}
|
||||
|
||||
`@workspace/record` is a UI-free React record context layer. A record is not restricted to an object: numbers, booleans, strings, arrays, functions, and plain objects are all valid; only `undefined` and `null` represent absence. Descendants can read the current value without passing a `record` prop through every level.
|
||||
|
||||
## Capabilities {#capabilities}
|
||||
|
||||
- Provide the current record with `RecordProvider`.
|
||||
- Read context with `useRecord`, or override it with an explicit record.
|
||||
- Read nested fields through typed dot paths with `useFieldValue`.
|
||||
- Create an isolated record context for each list item with `RecordsIterator`.
|
||||
- Make components work standalone and inside existing contexts with `OptionalRecordProvider`.
|
||||
|
||||
## Design boundary {#design-boundary}
|
||||
|
||||
This package does not fetch, cache, paginate, manage forms, or render UI. A data layer only needs to pass loaded values to a provider or iterator. `undefined` and `null` mean that no record is available; `0`, `false`, and an empty string remain valid records. Read live form edits from form state instead.
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: 嵌套上下文与稳定标识
|
||||
description: 组合局部记录上下文,并为记录列表选择稳定的 React key。
|
||||
order: 20
|
||||
toc:
|
||||
- id: nested-records
|
||||
title: 嵌套记录
|
||||
- id: optional-provider
|
||||
title: 可选 Provider
|
||||
- id: stable-keys
|
||||
title: 稳定标识
|
||||
---
|
||||
|
||||
## 嵌套记录 {#nested-records}
|
||||
|
||||
Provider 可以嵌套。内层字段读取局部记录,离开内层边界后恢复外层记录。
|
||||
|
||||
```tsx
|
||||
<RecordProvider value={order}>
|
||||
<OrderNumber />
|
||||
|
||||
<RecordProvider value={order.customer}>
|
||||
<CustomerName />
|
||||
</RecordProvider>
|
||||
</RecordProvider>
|
||||
```
|
||||
|
||||
不要把记录上下文当作可变 Store。新数据到达时,把新的记录引用传给 Provider;需要编辑的字段交给表单或其他状态层管理。
|
||||
|
||||
## 可选 Provider {#optional-provider}
|
||||
|
||||
`OptionalRecordProvider` 只在 `value` 不是 `undefined` 或 `null` 时创建新边界。值为空时,它保留最近的父级记录;`0`、`false` 和空字符串仍会创建新边界。这适合既能接收 prop、又能嵌入现有记录界面的组件。
|
||||
|
||||
```tsx
|
||||
function ProductCard({ product }: { product?: Product }) {
|
||||
return (
|
||||
<OptionalRecordProvider value={product}>
|
||||
<ProductTitle />
|
||||
<ProductPrice />
|
||||
</OptionalRecordProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 稳定标识 {#stable-keys}
|
||||
|
||||
`RecordsIterator` 会直接使用字符串、数字或 bigint 记录作为 key;对于对象记录,它默认读取 `id`。其他值或没有 `id` 的对象可通过回调提供 `recordKey`,对象也可以提供字段名。只有未获得有效标识时才会回退到数组下标。
|
||||
|
||||
```tsx
|
||||
<RecordsIterator
|
||||
data={locales}
|
||||
iterator={(locale) => <LocaleRow />}
|
||||
recordKey="code"
|
||||
/>
|
||||
|
||||
<RecordsIterator
|
||||
data={revisions}
|
||||
recordKey={(revision) => `${revision.documentId}:${revision.version}`}
|
||||
>
|
||||
<RevisionRow />
|
||||
</RecordsIterator>
|
||||
```
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: 记录列表示例
|
||||
description: 使用共享字段组件渲染带上下文的记录列表。
|
||||
order: 30
|
||||
toc:
|
||||
- id: define-field-components
|
||||
title: 定义字段组件
|
||||
- id: render-the-list
|
||||
title: 渲染列表
|
||||
- id: iterator-callback
|
||||
title: 使用迭代回调
|
||||
- id: add-separators
|
||||
title: 添加分隔内容
|
||||
---
|
||||
|
||||
## 定义字段组件 {#define-field-components}
|
||||
|
||||
字段组件只依赖记录上下文,可以复用于详情页、列表和嵌套对象。
|
||||
|
||||
```tsx
|
||||
function ProductName() {
|
||||
const name = useFieldValue<Product, "name">({ source: "name" })
|
||||
return <strong>{name}</strong>
|
||||
}
|
||||
|
||||
function ProductPrice() {
|
||||
const price = useFieldValue<Product, "price">({ source: "price" })
|
||||
return <span>¥{price}</span>
|
||||
}
|
||||
```
|
||||
|
||||
## 渲染列表 {#render-the-list}
|
||||
|
||||
把相同的 children 传给 Iterator,它会为每条记录重新提供一次上下文。
|
||||
|
||||
```tsx
|
||||
<ul>
|
||||
<RecordsIterator data={products}>
|
||||
<li>
|
||||
<ProductName />
|
||||
<ProductPrice />
|
||||
</li>
|
||||
</RecordsIterator>
|
||||
</ul>
|
||||
```
|
||||
|
||||
当 `data` 为空、未定义,或没有提供 children/iterator 时,Iterator 返回 `null`。加载、错误和空状态由外层请求界面处理。
|
||||
|
||||
## 使用迭代回调 {#iterator-callback}
|
||||
|
||||
简单场景可以直接读取回调参数。回调仍然位于对应记录的上下文中,因此内部组件可以继续使用 Hooks。
|
||||
|
||||
```tsx
|
||||
<RecordsIterator
|
||||
data={products}
|
||||
iterator={(product, index) => (
|
||||
<article>
|
||||
<span>#{index + 1}</span>
|
||||
<ProductName />
|
||||
<button onClick={() => addToCart(product.id)}>Add</button>
|
||||
</article>
|
||||
)}
|
||||
/>
|
||||
```
|
||||
|
||||
## 添加分隔内容 {#add-separators}
|
||||
|
||||
使用 `separator` 在相邻记录之间插入分隔内容。它不会在第一条记录之前或最后一条记录之后渲染。回调参数是分隔符后面那条记录的索引,并且分隔内容位于对应记录的 `RecordProvider` 之外。
|
||||
|
||||
```tsx
|
||||
<RecordsIterator
|
||||
data={products}
|
||||
separator={(index) => <hr aria-label={`第 ${index} 个分隔符`} />}
|
||||
>
|
||||
<ProductCard />
|
||||
</RecordsIterator>
|
||||
```
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: 提供并读取记录
|
||||
description: 使用 RecordProvider、useRecord 和 useFieldValue 构建记录感知组件。
|
||||
order: 10
|
||||
toc:
|
||||
- id: provide-a-record
|
||||
title: 提供记录
|
||||
- id: read-a-record
|
||||
title: 读取记录
|
||||
- id: read-a-field
|
||||
title: 读取字段
|
||||
---
|
||||
|
||||
## 提供记录 {#provide-a-record}
|
||||
|
||||
在获得数据的边界挂载 Provider。`RecordProvider` 接受已经存在的非空记录;请求结果可能为空时,使用 `OptionalRecordProvider`。
|
||||
|
||||
```tsx
|
||||
import { RecordProvider } from "@workspace/record"
|
||||
|
||||
function ProductScreen({ product }: { product: Product }) {
|
||||
return (
|
||||
<RecordProvider value={product}>
|
||||
<ProductDetails />
|
||||
</RecordProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
import { OptionalRecordProvider } from "@workspace/record"
|
||||
|
||||
;<OptionalRecordProvider value={query.data}>
|
||||
<ProductDetails />
|
||||
</OptionalRecordProvider>
|
||||
```
|
||||
|
||||
## 读取记录 {#read-a-record}
|
||||
|
||||
`useRecord` 在没有 Provider 或数据尚未加载时返回 `undefined`。
|
||||
|
||||
```tsx
|
||||
function ProductDetails() {
|
||||
const product = useRecord<Product>()
|
||||
if (!product) return <p>Loading…</p>
|
||||
|
||||
return <h2>{product.name}</h2>
|
||||
}
|
||||
```
|
||||
|
||||
记录感知组件也可以接受可选的 `record` prop。显式记录优先于上下文,方便单独预览和测试组件。
|
||||
|
||||
```tsx
|
||||
function ProductTitle({ record }: { record?: Product }) {
|
||||
const product = useRecord({ record })
|
||||
return product ? <h2>{product.name}</h2> : null
|
||||
}
|
||||
```
|
||||
|
||||
## 读取字段 {#read-a-field}
|
||||
|
||||
`useFieldValue` 支持直接字段、嵌套对象和数组数字段。路径使用点分隔。
|
||||
|
||||
```tsx
|
||||
const city = useFieldValue<Customer, "address.city">({
|
||||
source: "address.city",
|
||||
defaultValue: "Unknown",
|
||||
})
|
||||
|
||||
const firstSku = useFieldValue<Order, "items.0.sku">({
|
||||
source: "items.0.sku",
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: 概览
|
||||
description: 了解 Record 包如何在数据驱动界面中传递当前记录。
|
||||
order: 1
|
||||
toc:
|
||||
- id: package-role
|
||||
title: 包的职责
|
||||
- id: capabilities
|
||||
title: 主要能力
|
||||
- id: design-boundary
|
||||
title: 设计边界
|
||||
---
|
||||
|
||||
## 包的职责 {#package-role}
|
||||
|
||||
`@workspace/record` 是一个无 UI 的 React 记录上下文层。这里的“记录”不是特定对象类型:除 `undefined` 和 `null` 外,数字、布尔值、字符串、数组、函数和普通对象都可以作为记录。它让后代组件读取当前值,而不需要逐层传递 `record` prop。
|
||||
|
||||
## 主要能力 {#capabilities}
|
||||
|
||||
- 使用 `RecordProvider` 提供当前记录。
|
||||
- 使用 `useRecord` 读取上下文,或通过显式记录覆盖上下文。
|
||||
- 使用 `useFieldValue` 按类型安全的点路径读取嵌套字段。
|
||||
- 使用 `RecordsIterator` 为列表中的每条数据创建隔离的记录上下文。
|
||||
- 使用 `OptionalRecordProvider` 让组件同时支持独立使用和上下文组合。
|
||||
|
||||
## 设计边界 {#design-boundary}
|
||||
|
||||
该包不负责请求、缓存、分页、表单状态或 UI。请求层只需把已加载的值交给 Provider 或 Iterator。`undefined` 和 `null` 表示没有可提供的记录;`0`、`false` 和空字符串仍是有效记录。表单中的实时输入值仍应由表单状态读取。
|
||||
Reference in New Issue
Block a user