yann-core 0.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI_USAGE.md +145 -0
- package/README.md +226 -7
- package/dist/index.js +4 -563
- package/dist/types/index.d.ts +9 -2
- package/dist/types/useState/index.d.ts +34 -5
- package/dist/types/useTimer/index.d.ts +21 -5
- package/dist/types/utils/index.d.ts +79 -12
- package/dist/useState.js +531 -0
- package/dist/useTimer.js +95 -0
- package/package.json +23 -23
- package/dist/types/useI18n/index.d.ts +0 -1
package/AI_USAGE.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# yann-core:AI 使用指南
|
|
2
|
+
|
|
3
|
+
供 AI 编码助手在消费 yann-core 时读取。开发人员完整说明见 [README.md](./README.md)。本文描述库使用契约,不是修改仓库的代理工作指令。实际安装版本不同时,以对应版本的声明和实现为准。
|
|
4
|
+
|
|
5
|
+
## 库与公开入口
|
|
6
|
+
|
|
7
|
+
- 用途:Vue 响应式状态与浏览器动画帧定时器。
|
|
8
|
+
- Vue peer dependency:`^3.5.13`;包输出 ESM,提供 TypeScript 声明。
|
|
9
|
+
- 安装:`pnpm add yann-core vue@^3.5.13`;已有符合要求的 Vue 时只需添加 `yann-core`。
|
|
10
|
+
- 从公共根入口导入,不生成 `src/`、`dist/` 或内部工具路径导入。
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { useState, useRafTimer } from 'yann-core'
|
|
14
|
+
import type {
|
|
15
|
+
UseStateConfig,
|
|
16
|
+
UseStateRef,
|
|
17
|
+
UseStateReturn,
|
|
18
|
+
Callback,
|
|
19
|
+
ControlFunctions,
|
|
20
|
+
RafTimerOptions,
|
|
21
|
+
} from 'yann-core'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
公开运行时 API 只有 `useState` 和 `useRafTimer`,不要推测其他 Hook 或工具存在。
|
|
25
|
+
|
|
26
|
+
## 任务选择
|
|
27
|
+
|
|
28
|
+
| 需求 | 方案 |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| 可写 Vue 状态、相等跳过或写入转换 | `useState` |
|
|
31
|
+
| 大对象通过根值替换更新 | `useState(source, { shallow: true })` |
|
|
32
|
+
| 根据 props / getter 持续派生值 | Vue `computed` |
|
|
33
|
+
| 随来源变化同步可写状态 | 显式 Vue `watch` |
|
|
34
|
+
| 浏览器定时任务、串行轮询 | `useRafTimer`,返回或 await 异步任务 |
|
|
35
|
+
| 单次延后任务 | `useRafTimer(callback, delay, { loop: false })` |
|
|
36
|
+
| 服务端或后台可靠调度、精确时钟 | 使用对应环境的调度方案;本 Hook 无 RAF 时不执行 |
|
|
37
|
+
|
|
38
|
+
## useState 契约
|
|
39
|
+
|
|
40
|
+
以下是签名摘要,实际声明包含深层、浅层和动态布尔配置的重载:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// MaybeRefOrGetter 来自 Vue;返回引用按 shallow 推导
|
|
44
|
+
useState<T>(source: MaybeRefOrGetter<T>, config?: UseStateConfig<T, boolean>)
|
|
45
|
+
// 返回 [state, setState];setState: (incoming: T) => boolean
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
1. 脚本用 `state.value` 读值,模板自动解包。setter 接受完整新值,不接受 `(previous) => next` 更新器。
|
|
49
|
+
2. 初始来源通过 Vue `toValue` 解析一次,没有持续同步 watcher,也不深拷贝对象。getter 返回 Ref 时可能复用该 Ref。
|
|
50
|
+
3. `shallow` 默认 `false`:深层 `ref` 解包对象属性中的嵌套 Ref;数组 / Map 遵循 Vue 自身规则。`true` 使用 `shallowRef`,保留普通对象和嵌套 Ref;已有响应式对象保持响应式。
|
|
51
|
+
4. `diff` 默认 `false`。`diff: true` 使用 `Object.is` 或 Lodash `isEqual` 判等。提供 `compare` 就启用比较并替代默认比较,即使 `diff: false` 也如此。
|
|
52
|
+
5. `compare(oldValue, incoming)` 返回 **true 表示相等并跳过**。旧值是状态读取类型,新值是原始输入类型;每次 setter 调用都执行自定义比较器。
|
|
53
|
+
6. setter 顺序:比较旧值与原始输入 → 相等则返回 `false` → `beforeSet(incoming)` 转换 → `unref` 解包根 Ref → 写入并返回 `true`。比较在转换之前。
|
|
54
|
+
7. `true` 表示接受设置,不保证实际变化或视图更新。比较器 / 转换器异常直接传给调用方。
|
|
55
|
+
8. 直接修改 `state.value` 或内部属性会绕过 setter 的比较与转换。比较模式下应构造新对象;原地修改再传同一对象无法比较修改前的快照。
|
|
56
|
+
9. 浅层普通对象内部修改不触发根引用更新;优先替换根值,或显式使用 Vue `triggerRef`。
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const [count, setCount] = useState(0)
|
|
60
|
+
setCount(count.value + 1)
|
|
61
|
+
|
|
62
|
+
const [rows, setRows] = useState<Array<{ id: number }>>([], { shallow: true })
|
|
63
|
+
setRows([...rows.value, { id: 1 }])
|
|
64
|
+
|
|
65
|
+
const [value, setValue] = useState(2, {
|
|
66
|
+
diff: true,
|
|
67
|
+
beforeSet: (incoming) => incoming * 2,
|
|
68
|
+
})
|
|
69
|
+
setValue(2) // false,转换不执行
|
|
70
|
+
setValue(1) // true,转换后仍是 2
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`UseStateConfig<T, Shallow>`、`UseStateRef<T, Shallow>`、`UseStateReturn<T, Shallow>` 的第二个参数默认 `false`;浅层用 `true`,动态开关用 `boolean`。不要用类型断言掩盖嵌套 Ref 解包差异。
|
|
74
|
+
|
|
75
|
+
## useRafTimer 契约
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
type Callback = () => void | PromiseLike<void>
|
|
79
|
+
interface ControlFunctions {
|
|
80
|
+
stop: () => void
|
|
81
|
+
pause: () => void
|
|
82
|
+
start: () => void
|
|
83
|
+
}
|
|
84
|
+
interface RafTimerOptions {
|
|
85
|
+
type?: 'waitAfterCallback' | 'ignoreCallbackTime'
|
|
86
|
+
loop?: boolean | number
|
|
87
|
+
onError?: (error: unknown) => void | PromiseLike<void>
|
|
88
|
+
}
|
|
89
|
+
// interval 默认 1000 / 60 毫秒
|
|
90
|
+
useRafTimer(callback: Callback, interval?: number, options?: RafTimerOptions): ControlFunctions
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- 自动启动,首次回调在间隔到期后的动画帧执行,不立即执行。立即加载数据应由业务显式实现。
|
|
94
|
+
- 默认 `type: 'ignoreCallbackTime'`、`loop: true`。普通请求轮询可选 `waitAfterCallback`,在回调及错误报告完成后再等待完整间隔。
|
|
95
|
+
- `ignoreCallbackTime` 从本次开始计算下一次间隔,但仍等待当前异步回调结束;不会并发,也不补发遗漏次数。
|
|
96
|
+
- 必须返回 Promise / thenable 才能被等待。不要生成 `() => { void fetchData() }` 并声称它串行等待请求。
|
|
97
|
+
- `pause()` 冻结计时,保留剩余等待时间;`start()` 仅恢复暂停;`stop()` 永久停止,之后 `start()` 无效。恢复停止的任务必须新建实例。
|
|
98
|
+
- 控制器没有 `resume`、`restart`、`isRunning` 或动态更新间隔等 API,不要虚构。
|
|
99
|
+
- `loop: false` 一次;`true` 或 `Infinity` 无限;有限数字向上取整且至少一次(`0` 和负有限数也是一次)。失败尝试也计入次数。
|
|
100
|
+
- interval 必须有限且非负,零间隔每帧最多一次;无效数值抛 `RangeError`。loop 的 `NaN` / 负无穷也抛 `RangeError`。
|
|
101
|
+
- 回调异常通过 `onError` 或默认 `console.error` 报告后继续。异步错误处理器会被等待,其自身异常被捕获并记录。
|
|
102
|
+
- 缺少 RAF 或 cancelRAF 时返回空操作控制器,不执行回调,参数校验仍执行。不要宣称 SSR / Node 中自动降级为可执行的 timeout 定时器。
|
|
103
|
+
- 活跃 Vue effect scope 中同步创建会注册自动清理;作用域外需要手动停止。不要依赖异步 await 后创建时的自动清理。
|
|
104
|
+
- 停止不取消正在运行的 Promise,也不阻止已开始的回调写回数据;按业务需要用 `AbortController` 和销毁标记管理取消及结果保护。
|
|
105
|
+
- 精度受帧率与后台限流影响,长等待结合 timeout 与 RAF;不能承诺精确时间或后台可靠执行。
|
|
106
|
+
|
|
107
|
+
## 可采用的 Vue 示例
|
|
108
|
+
|
|
109
|
+
```vue
|
|
110
|
+
<script setup lang="ts">
|
|
111
|
+
import { useRafTimer, useState } from 'yann-core'
|
|
112
|
+
|
|
113
|
+
const [count, setCount] = useState(0)
|
|
114
|
+
const timer = useRafTimer(() => {
|
|
115
|
+
setCount(count.value + 1)
|
|
116
|
+
}, 1000, { loop: true })
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<template>
|
|
120
|
+
<p>{{ count }}</p>
|
|
121
|
+
<button @click="timer.pause">暂停</button>
|
|
122
|
+
<button @click="timer.start">恢复</button>
|
|
123
|
+
<button @click="timer.stop">永久停止</button>
|
|
124
|
+
</template>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
请求轮询及取消的完整示例见 [README 生命周期与取消请求](./README.md#生命周期与取消请求)。生成实际请求代码时,应按业务声明数据类型、检查 HTTP 状态,并确定卸载后是否允许结果写回。
|
|
128
|
+
|
|
129
|
+
## 生成代码前核对
|
|
130
|
+
|
|
131
|
+
- 只从公共入口导入已公开 API。
|
|
132
|
+
- setter 使用新值;比较器 true 意味相等跳过;beforeSet 在比较之后执行。
|
|
133
|
+
- 深层与浅层嵌套 Ref 访问方式正确,浅层更新通过替换根值或 triggerRef。
|
|
134
|
+
- 没有把初始化 getter 写成持续同步机制。
|
|
135
|
+
- 定时器自动启动且首次不立即执行,暂停、恢复、永久停止的交互语义正确。
|
|
136
|
+
- 异步回调返回或 await 任务;作用域外显式清理;请求取消由业务自行管理。
|
|
137
|
+
- 不将 setter 返回值解释为实际渲染,不承诺精确时钟、SSR 执行或 Promise 自动取消。
|
|
138
|
+
|
|
139
|
+
## 仓库内核对依据
|
|
140
|
+
|
|
141
|
+
API 以 [公共入口](./src/index.ts)、[状态实现](./src/useState/index.ts)、[定时器实现](./src/useTimer/index.ts) 为准。类型边界见 [状态类型用例](./tests/state.types.ts),行为见 [状态测试](./tests/useState.test.ts) 与 [定时器测试](./tests/useTimer.test.ts)。内部 `src/utils/` 的函数不是公开 API。
|
|
142
|
+
|
|
143
|
+
在本仓库改动示例或 API 后可运行 `pnpm type-check`、`pnpm test`;核对发布产物时先 `pnpm build`,再 `pnpm verify:package`。消费项目应运行自己的类型检查与测试。
|
|
144
|
+
|
|
145
|
+
当前 `package.json` 的 `files` 仅包含 `dist`;本文供从仓库读取或作为上下文提供给 AI,不要假定 npm 安装目录包含此文件。
|
package/README.md
CHANGED
|
@@ -1,15 +1,234 @@
|
|
|
1
1
|
# yann-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
面向 Vue 3.5 的 TypeScript 工具库,提供支持异步回调、暂停和作用域清理的定时器,以及带差异比较和写入转换的响应式状态。
|
|
4
|
+
|
|
5
|
+
| API | 用途 |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| `useRafTimer` | 按指定间隔执行同步或异步任务 |
|
|
8
|
+
| `useState` | 创建状态,通过 setter 比较、转换和写入新值 |
|
|
9
|
+
|
|
10
|
+
AI 编码助手请同时阅读 [AI 使用指南](./AI_USAGE.md)。
|
|
11
|
+
|
|
12
|
+
## 安装与环境
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pnpm add yann-core vue@^3.5.13
|
|
16
|
+
# 或:npm install yann-core vue@^3.5.13
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Vue 是 peer dependency,版本要求为 `^3.5.13`,使用项目自身的 Vue 实例。包输出为 ESM,附带 TypeScript 声明;统一从 `yann-core` 根入口导入。
|
|
20
|
+
|
|
21
|
+
定时器依赖浏览器的 `requestAnimationFrame` 和 `cancelAnimationFrame`。缺少任一 API 时(如通常的 SSR 环境),返回空操作控制器,不执行回调。
|
|
22
|
+
|
|
23
|
+
## 快速开始
|
|
24
|
+
|
|
25
|
+
在 Vue 单文件组件的 `<script setup>` 中同步创建 Hook,定时器会随组件作用域销毁自动停止。
|
|
26
|
+
|
|
27
|
+
```vue
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
import { useRafTimer, useState } from 'yann-core'
|
|
30
|
+
|
|
31
|
+
const [count, setCount] = useState(0)
|
|
32
|
+
const timer = useRafTimer(() => {
|
|
33
|
+
setCount(count.value + 1)
|
|
34
|
+
}, 1000)
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<p>计数:{{ count }}</p>
|
|
39
|
+
<button @click="timer.pause">暂停</button>
|
|
40
|
+
<button @click="timer.start">恢复</button>
|
|
41
|
+
<button @click="timer.stop">永久停止</button>
|
|
42
|
+
</template>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
创建后自动开始,首次回调需等待间隔到期。`start()` 恢复暂停,停止后需要重新创建定时器。
|
|
46
|
+
|
|
47
|
+
## 定时器
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { useRafTimer } from 'yann-core'
|
|
51
|
+
|
|
52
|
+
const timer = useRafTimer(async () => {
|
|
53
|
+
const response = await fetch('/api/status')
|
|
54
|
+
if (!response.ok) throw new Error(`请求失败:${response.status}`)
|
|
55
|
+
// 在这里处理返回结果
|
|
56
|
+
}, 1000, {
|
|
57
|
+
type: 'ignoreCallbackTime',
|
|
58
|
+
loop: true,
|
|
59
|
+
onError: (error) => console.error(error),
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
timer.pause()
|
|
63
|
+
timer.start() // 恢复暂停
|
|
64
|
+
timer.stop() // 永久停止;start 不会重新启动
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 参数与控制方法
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
useRafTimer(callback, interval?, options?)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
| 参数 / 配置 | 默认值 | 说明 |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| `callback` | 必填 | `() => void \| PromiseLike<void>`,同步回调或返回 Promise / thenable 的异步回调 |
|
|
76
|
+
| `interval` | `1000 / 60` | 毫秒,有限非负数;零间隔每帧最多执行一次 |
|
|
77
|
+
| `options.type` | `'ignoreCallbackTime'` | 从本次回调开始计算下一次间隔;`'waitAfterCallback'` 从回调及错误报告完成后计算 |
|
|
78
|
+
| `options.loop` | `true` | `true` / `Infinity` 无限循环,`false` 一次,有限数字向上取整且至少一次 |
|
|
79
|
+
| `options.onError` | 无 | `(error: unknown) => void \| PromiseLike<void>`,未提供时使用 `console.error` |
|
|
80
|
+
|
|
81
|
+
| 方法 | 行为 |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| `pause()` | 暂停并冻结计时,保留剩余等待时间;不取消正在执行的异步任务 |
|
|
84
|
+
| `start()` | 从暂停处恢复;若回调尚未完成,继续等待,不会重入 |
|
|
85
|
+
| `stop()` | 永久停止并清理待调度任务;不能通过 `start()` 重启 |
|
|
86
|
+
|
|
87
|
+
### 调度与错误行为
|
|
88
|
+
|
|
89
|
+
- 创建后自动开始,首次执行在间隔到期后的动画帧;默认间隔为 `1000 / 60` 毫秒。
|
|
90
|
+
- `ignoreCallbackTime`:从本次回调开始计算间隔;`waitAfterCallback`:从回调及错误报告完成后计算间隔。
|
|
91
|
+
- 每个实例最多执行一个回调,支持 Promise 和 thenable。长回调完成后若已过期,只在下一帧执行一次,不补发遗漏次数。
|
|
92
|
+
- 暂停冻结计时;恢复时仍等待尚未完成的回调。`stop` 和 Vue 作用域销毁会清理待调度任务并释放回调引用;用户创建的 Promise 不会自动取消。
|
|
93
|
+
- 异常报告后继续,未配置 `onError` 时使用 `console.error`。异步 `onError` 也会等待;报告器失败会捕获并记录。每次回调尝试,包括失败,都计入次数。
|
|
94
|
+
- `loop: true` 或 `Infinity` 无限循环,`false` 执行一次;有限数字向上取整且至少一次。`NaN` 和负无穷非法。
|
|
95
|
+
- 间隔必须是有限非负数;零间隔每帧最多执行一次。超长延时分段等待。无 RAF 环境(如 SSR)返回无操作的控制方法,不执行回调。
|
|
96
|
+
- 长间隔由 timeout 等待,距离截止时间 20ms 内由 RAF 检查。实际执行精度受帧率和浏览器后台限流影响。
|
|
97
|
+
|
|
98
|
+
回调必须返回 Promise / thenable 才能被等待;不要在回调中只启动异步任务而不返回,例如 `() => { void fetchData() }`。需要串行轮询时,使用 `async` 回调并 `await` 请求,通常选择 `waitAfterCallback`。
|
|
99
|
+
|
|
100
|
+
非法间隔或 `loop: NaN` / `loop: -Infinity` 会抛出 `RangeError`;无 RAF 环境中也会先校验参数。控制器只有 `stop`、`pause`、`start`,没有运行状态 Ref 或动态修改间隔的方法。本 Hook 不适合作为精确时钟或服务端调度器。
|
|
101
|
+
|
|
102
|
+
### 生命周期与取消请求
|
|
103
|
+
|
|
104
|
+
在活跃 Vue effect scope 中同步创建时,作用域销毁会自动停止。在组件外、作用域外或异步 `await` 之后创建时,不应假定自动清理,应自行调用 `stop()`。
|
|
105
|
+
|
|
106
|
+
停止不会取消用户创建的 Promise,也不会阻止已开始的回调继续写回数据。请求取消与结果保护由业务管理:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { onScopeDispose } from 'vue'
|
|
110
|
+
import { useRafTimer, useState } from 'yann-core'
|
|
111
|
+
|
|
112
|
+
// 在 setup 或活跃 effect scope 内同步执行
|
|
113
|
+
const [status, setStatus] = useState<unknown>(null)
|
|
114
|
+
const controller = new AbortController()
|
|
115
|
+
let disposed = false
|
|
116
|
+
|
|
117
|
+
const timer = useRafTimer(async () => {
|
|
118
|
+
const response = await fetch('/api/status', { signal: controller.signal })
|
|
119
|
+
if (!response.ok) throw new Error(`请求失败:${response.status}`)
|
|
120
|
+
const data: unknown = await response.json()
|
|
121
|
+
if (!disposed) setStatus(data)
|
|
122
|
+
}, 1000, { type: 'waitAfterCallback' })
|
|
123
|
+
|
|
124
|
+
const dispose = () => {
|
|
125
|
+
disposed = true
|
|
126
|
+
timer.stop()
|
|
127
|
+
controller.abort()
|
|
128
|
+
}
|
|
129
|
+
onScopeDispose(dispose)
|
|
130
|
+
// 如需主动结束轮询,也调用 dispose()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## 状态
|
|
134
|
+
|
|
135
|
+
`useState(source, config?)` 返回 `[state, setState]`。输入为普通值、Vue Ref 或 getter;`state` 是 Vue 响应式引用,在脚本中通过 `.value` 读取,在模板中自动解包。
|
|
4
136
|
|
|
5
137
|
```ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
138
|
+
import { useState } from 'yann-core'
|
|
139
|
+
|
|
140
|
+
const [state, setState] = useState(0)
|
|
141
|
+
setState(1)
|
|
142
|
+
|
|
143
|
+
const [rows, setRows] = useState([{ id: 1 }], { shallow: true })
|
|
144
|
+
setRows([{ id: 2 }]) // 替换 .value 触发更新
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 配置与更新规则
|
|
148
|
+
|
|
149
|
+
| 配置 | 默认值 | 说明 |
|
|
150
|
+
| --- | --- | --- |
|
|
151
|
+
| `shallow` | `false` | `true` 使用 `shallowRef`,否则使用深层 `ref` |
|
|
152
|
+
| `diff` | `false` | 启用相等比较,相等时跳过设置 |
|
|
153
|
+
| `compare(oldValue, incoming)` | 无 | 自定义比较;返回 `true` 表示相等。提供后即启用比较,不必设置 `diff` |
|
|
154
|
+
| `beforeSet(incoming)` | 无 | 接受原始输入,返回加工后的新值;仅在比较未跳过时调用 |
|
|
155
|
+
|
|
156
|
+
setter 接受完整新值,不支持 React 风格的 `setState(previous => next)` 更新器。直接修改 `state.value` 或内部属性会绕过比较与转换。比较模式下应构造新对象;原地修改后传同一对象,默认比较无法检测修改前的内容。
|
|
157
|
+
|
|
158
|
+
### 深层、浅层与初始化来源
|
|
159
|
+
|
|
160
|
+
默认使用深层响应式 `ref`,对象属性中的嵌套 Ref 会解包;数组和 Map 中的 Ref 遵循 Vue 自身的规则。`shallow: true` 使用 `shallowRef`,保留输入对象与嵌套 Ref,不为普通对象增加深层代理。已有响应式对象不会被转换回普通对象。浅层内部修改需替换对象或使用 Vue 的 `triggerRef`。
|
|
161
|
+
|
|
162
|
+
普通值、Ref 和 getter 只在初始化时求值一次,不持续同步源。若 getter 返回 Ref,则遵循 Vue 工厂的 Ref 复用行为;setter 收到根 Ref 时取其内部值再写入,保持读取类型与运行结果一致。比较与 `beforeSet` 仍接收原始输入。
|
|
163
|
+
|
|
164
|
+
例如 `useState(() => props.initialCount)` 只读取初始值;持续派生值用 Vue `computed`,持续同步可写状态用显式 `watch`。状态不是输入对象的深拷贝。
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { ref, triggerRef } from 'vue'
|
|
168
|
+
|
|
169
|
+
const [deep] = useState({ nested: ref(1) })
|
|
170
|
+
console.log(deep.value.nested) // 1
|
|
171
|
+
|
|
172
|
+
const [shallow] = useState({ nested: ref(1) }, { shallow: true })
|
|
173
|
+
console.log(shallow.value.nested.value) // 1
|
|
174
|
+
|
|
175
|
+
const [items] = useState([{ id: 1 }], { shallow: true })
|
|
176
|
+
items.value.push({ id: 2 })
|
|
177
|
+
triggerRef(items) // 普通浅层对象内部修改后,手动通知依赖
|
|
9
178
|
```
|
|
10
179
|
|
|
11
|
-
|
|
180
|
+
### 比较顺序与类型
|
|
12
181
|
|
|
13
182
|
```ts
|
|
14
|
-
const [
|
|
15
|
-
|
|
183
|
+
const [value, setValue] = useState(2, {
|
|
184
|
+
diff: true,
|
|
185
|
+
beforeSet: (incoming) => incoming * 2,
|
|
186
|
+
})
|
|
187
|
+
setValue(2) // false:比较相等,beforeSet 不执行
|
|
188
|
+
setValue(1) // true:先比较原始输入,再转换为 2
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`diff: true` 默认深比较(支持数组、Map、循环引用),先做引用相等快速判断。提供 `compare(oldValue, newValue)` 时始终调用自定义比较器,并启用比较流程,即使未设置 `diff`。旧值类型与状态读取类型一致,新值和 `beforeSet` 使用原始输入类型。
|
|
192
|
+
|
|
193
|
+
完整顺序:**比较旧值与原始输入 → 相等则跳过 → beforeSet 转换 → 根 Ref 解包 → 写入**。比较器与转换器抛出的异常会传给调用方。
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import type { UseStateConfig, UseStateReturn } from 'yann-core'
|
|
197
|
+
|
|
198
|
+
type User = { id: number; name: string }
|
|
199
|
+
const config: UseStateConfig<User> = {
|
|
200
|
+
compare: (oldValue, incoming) =>
|
|
201
|
+
oldValue.id === incoming.id && oldValue.name === incoming.name,
|
|
202
|
+
}
|
|
203
|
+
const result: UseStateReturn<User> = useState({ id: 1, name: 'Alice' }, config)
|
|
204
|
+
result[1]({ id: 1, name: 'Alice' }) // false
|
|
205
|
+
result[1]({ id: 1, name: 'Bob' }) // true
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
setter 返回 `false` 表示比较判定相等而跳过设置,返回 `true` 表示接受设置;未启用比较时,即使 Vue 本身不触发更新,也返回 `true`。类型从根入口导出:`UseStateConfig<T, Shallow>`、`UseStateRef<T, Shallow>`、`UseStateReturn<T, Shallow>`、`Callback`、`ControlFunctions`、`RafTimerOptions`。状态类型第二个参数默认 `false`,动态布尔选项使用 `boolean`。
|
|
209
|
+
|
|
210
|
+
类型纠错可能使依赖旧声明的代码报错:深层模式的对象 Ref 属性应读取 `state.value.nested`,而不是 `state.value.nested.value`;比较器旧值同样遵循解包类型。
|
|
211
|
+
|
|
212
|
+
## 验证
|
|
213
|
+
|
|
214
|
+
以下命令用于维护本仓库;消费项目运行自己的构建和类型检查。
|
|
215
|
+
|
|
216
|
+
```sh
|
|
217
|
+
pnpm install --frozen-lockfile
|
|
218
|
+
pnpm type-check
|
|
219
|
+
pnpm lint:check
|
|
220
|
+
pnpm test
|
|
221
|
+
pnpm build
|
|
222
|
+
pnpm verify:package
|
|
223
|
+
pnpm benchmark
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`type-check` 同时编译运行测试和类型回归用例;`lint:check` 不修改文件。`verify:package` 检查打包后的 ESM、NodeNext/Bundler 类型消费和仅导入定时器时的 tree shaking。基准需要支持 `require(ESM)` 的 Node 22.12+ 及 Git 基线历史;可用 `YANN_BASELINE_REVISION` 指定比较提交。结果写入忽略的 `artifacts/`,方法和实测结果见 [PERFORMANCE.md](./PERFORMANCE.md)。
|
|
227
|
+
|
|
228
|
+
构建内存与体积可用 `node scripts/measure-build.mjs optimized` 记录;该命令清理 TypeScript 增量缓存以测量冷类型检查。不自动发布版本。
|
|
229
|
+
|
|
230
|
+
公共入口为 `src/index.ts`,Hook 位于 `src/useState/` 与 `src/useTimer/`,行为与类型用例位于 `tests/`。`build` 包含类型检查,输出 ESM 和类型声明到 `dist/`;`verify:package` 应在构建后运行。
|
|
231
|
+
|
|
232
|
+
## 许可证
|
|
233
|
+
|
|
234
|
+
[MIT](./LICENSE)
|