@variojs/vue 0.0.1 → 0.0.4

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/README.md CHANGED
@@ -1,915 +1,120 @@
1
- # @vario/vue
1
+ # 🎨 @variojs/vue
2
2
 
3
- Vario Vue Renderer - 深度集成 Vue 3 的 Schema 渲染器
3
+ Vario Vue 渲染器 - 深度集成 Vue 3 的 Schema 渲染器
4
4
 
5
- ## 简介
5
+ ## 特点
6
6
 
7
- `@vario/vue` Vario Schema 的 Vue 3 集成层,将声明式的 JSON Schema 转换为 Vue 组件树。它深度集成 Vue 3 的 Composition API,提供完整的 Vue 特性支持,同时保持 Schema 的简洁性和可维护性。
8
-
9
- ### 设计理念
10
-
11
- - **Schema 只负责结构**:Schema 定义 UI 结构、属性绑定、事件映射
12
- - **Vue 负责响应式逻辑**:computed、watch 等响应式特性使用 Vue 原生 API
13
- - **声明映射而非重新实现**:ref、生命周期、provide/inject 等通过声明映射到 Vue 原生 API
14
- - **自动响应式同步**:state 自动包裹为响应式对象,支持层级依赖收集
7
+ - 🚀 **深度集成**:完整支持 Vue 3 Composition API
8
+ - 📦 **声明式 Schema**:JSON Schema 定义 UI,简洁易维护
9
+ - 🔄 **自动响应式**:状态自动包裹为响应式,支持层级依赖收集
10
+ - 🎯 **Vue 特性支持**:ref、生命周期、provide/inject、teleport 等
15
11
 
16
12
  ## 安装
17
13
 
18
14
  ```bash
19
- npm install @vario/vue @vario/core @vario/schema
15
+ npm install @variojs/vue
20
16
  # 或
21
- pnpm add @vario/vue @vario/core @vario/schema
17
+ pnpm add @variojs/vue
22
18
  ```
23
19
 
20
+ 依赖的 `@variojs/core`、`@variojs/schema` 和 `vue` 会自动安装。
21
+
24
22
  ## 快速开始
25
23
 
26
24
  ```typescript
27
- import { useVario } from '@vario/vue'
28
- import type { VueSchemaNode } from '@vario/vue'
25
+ import { useVario } from '@variojs/vue'
26
+ import type { VueSchemaNode } from '@variojs/vue'
29
27
 
30
28
  const schema: VueSchemaNode = {
31
29
  type: 'div',
32
30
  children: [
33
31
  {
34
- type: 'input',
32
+ type: 'ElInput',
35
33
  model: 'name',
36
34
  props: { placeholder: '请输入姓名' }
37
35
  },
38
36
  {
39
37
  type: 'div',
40
38
  children: '{{ name }}'
41
- }
42
- ]
43
- }
44
-
45
- export default {
46
- setup() {
47
- const { vnode, state } = useVario(schema, {
48
- state: {
49
- name: ''
50
- }
51
- })
52
-
53
- return { vnode, state }
54
- }
55
- }
56
- ```
57
-
58
- ## 核心概念
59
-
60
- ### 状态管理
61
-
62
- `useVario` 会自动将传入的 `state` 包裹为 Vue 的响应式对象(`reactive`),并建立与运行时上下文(`RuntimeContext`)的双向同步。
63
-
64
- ```typescript
65
- const { state, ctx } = useVario(schema, {
66
- state: {
67
- user: {
68
- name: 'John',
69
- age: 30
70
- }
71
- }
72
- })
73
-
74
- // state 是响应式的
75
- state.user.name = 'Jane' // ✅ Vue 会自动追踪变化
76
-
77
- // 也可以通过 ctx 访问
78
- ctx._get('user.name') // 'Jane'
79
- ctx._set('user.age', 31) // ✅ 会同步到 state.user.age
80
- ```
81
-
82
- ### Model 双向绑定与依赖收集
83
-
84
- **关键特性:会根据层级自动收集依赖**
85
-
86
- 当你在 Schema 中使用 `model` 双向绑定时,Vario 会:
87
-
88
- 1. **自动追踪嵌套路径**:使用 Vue 的 Proxy 响应式系统追踪深层属性访问
89
- 2. **双向同步**:`ctx._set` 的变化会通过 `onStateChange` 同步到 `reactiveState`
90
- 3. **层级依赖收集**:Vue 会自动追踪所有访问的嵌套路径
91
-
92
- ```typescript
93
- const schema: VueSchemaNode = {
94
- type: 'div',
95
- children: [
96
- {
97
- type: 'input',
98
- model: 'user.profile.name', // 深层路径
99
- props: { placeholder: '姓名' }
100
- },
101
- {
102
- type: 'input',
103
- model: 'user.profile.email', // 另一个深层路径
104
- props: { placeholder: '邮箱' }
105
- },
106
- {
107
- type: 'div',
108
- children: '{{ user.profile.name }} - {{ user.profile.email }}'
109
- }
110
- ]
111
- }
112
-
113
- const { state } = useVario(schema, {
114
- state: {
115
- user: {
116
- profile: {
117
- name: '',
118
- email: ''
119
- }
120
- }
121
- }
122
- })
123
-
124
- // 当用户在输入框中输入时:
125
- // 1. 触发 update:modelValue 事件
126
- // 2. 调用 ctx._set('user.profile.name', newValue)
127
- // 3. 触发 onStateChange 回调
128
- // 4. 通过 setPathValue 更新 reactiveState.user.profile.name
129
- // 5. Vue 检测到变化,自动更新所有依赖该路径的组件
130
- // - 包括显示 {{ user.profile.name }} 的 div
131
- // - 包括绑定 model: 'user.profile.name' 的 input(值同步)
132
- ```
133
-
134
- **依赖收集机制详解**:
135
-
136
- ```typescript
137
- // 在 bindings.ts 中
138
- const value = getPathValue(getState(), modelPath)
139
- // getState() 返回 reactiveState(Vue 响应式对象)
140
- // 当 Vue 组件访问 reactiveState.user.profile.name 时
141
- // Vue 的 Proxy 会自动追踪这个嵌套路径的依赖
142
-
143
- // 在 composable.ts 中
144
- onStateChange: (path: string, value: unknown) => {
145
- setPathValue(
146
- reactiveState,
147
- path, // 'user.profile.name'
148
- value,
149
- {
150
- createObject: () => reactive({}), // 中间对象也是响应式的
151
- createArray: () => reactive([])
152
- }
153
- )
154
- }
155
- // 这样确保了整个路径链都是响应式的
156
- // reactiveState.user → reactive
157
- // reactiveState.user.profile → reactive
158
- // reactiveState.user.profile.name → 值
159
- ```
160
-
161
- **示例:深层嵌套绑定**
162
-
163
- ```typescript
164
- const schema: VueSchemaNode = {
165
- type: 'div',
166
- children: [
167
- {
168
- type: 'input',
169
- model: 'form.user.profile.contact.phone', // 4 层嵌套
170
- props: { placeholder: '电话' }
171
- },
172
- {
173
- type: 'div',
174
- children: '{{ form.user.profile.contact.phone }}'
175
- }
176
- ]
177
- }
178
-
179
- const { state } = useVario(schema, {
180
- state: {
181
- form: {
182
- user: {
183
- profile: {
184
- contact: {
185
- phone: ''
186
- }
187
- }
188
- }
189
- }
190
- }
191
- })
192
-
193
- // ✅ 所有层级都会自动收集依赖
194
- // ✅ 修改 state.form.user.profile.contact.phone 会触发更新
195
- // ✅ 在输入框中输入会同步到 state
196
- ```
197
-
198
- #### 自动路径解析与状态创建
199
-
200
- Vario 支持智能的 model 路径解析,可以自动拼接扁平路径,并自动创建缺失的状态结构。行为完全由 **model 的写法** 决定。
201
-
202
- **model 的几种形式**
203
-
204
- | 写法 | 是否压栈 | 是否在本节点绑定 | 典型用途 |
205
- |------|----------|------------------|----------|
206
- | **字符串路径** `model: "form"` | 压栈 | 绑定 | 普通表单项、嵌套路径根 |
207
- | **字符串** `model: "."` | 不压栈(用现有栈) | 栈非空时绑定到「当前栈路径」 | 循环中绑定到 `items[0]`、`items[1]` 等数组元素本身 |
208
- | **对象** `model: { path: "form", scope: true }` | 压栈 `path` | **不**绑定 | 仅作层级的容器(如表单根、区块) |
209
-
210
- 子节点的扁平路径会自动与父级路径栈(来自字符串 `model` 或 `model: { path, scope: true }`)拼接:
211
-
212
- ```typescript
213
- const schema: VueSchemaNode = {
214
- type: 'div',
215
- model: { path: 'form', scope: true }, // 父级仅作作用域,不绑定
216
- children: [
217
- {
218
- type: 'input',
219
- model: 'name' // 扁平路径 → 自动变成 form.name
220
- },
221
- {
222
- type: 'input',
223
- model: 'email' // 扁平路径 → 自动变成 form.email
224
- },
225
- {
226
- type: 'div',
227
- model: { path: 'user', scope: true }, // 再压一层 user,不绑定
228
- children: [
229
- {
230
- type: 'input',
231
- model: 'age' // 扁平路径 → 自动变成 form.user.age
232
- }
233
- ]
234
- }
235
- ]
236
- }
237
-
238
- const { state } = useVario(schema, {
239
- state: {} // 空状态,会自动创建结构
240
- })
241
-
242
- // ✅ state.form.name 会被自动创建
243
- // ✅ state.form.email 会被自动创建
244
- // ✅ state.form.user.age 会被自动创建
245
- ```
246
-
247
- **使用 scope 的容器(仅作用域、不绑定)**
248
-
249
- 希望某节点只提供路径层级、自身不参与绑定时,使用对象形式并设置 `scope: true`:
250
-
251
- ```typescript
252
- const schema: VueSchemaNode = {
253
- type: 'form',
254
- model: { path: 'form', scope: true }, // 仅作用域,不绑定
255
- children: [
256
- { type: 'input', model: 'name' }, // → form.name
257
- { type: 'input', model: 'email' } // → form.email
258
- ]
259
- }
260
- // ✅ 根节点不会对 state.form 做 v-model,仅子节点绑定 form.name / form.email
261
- ```
262
-
263
- **明确路径**
264
-
265
- 使用完整路径时,不会进行拼接,直接使用。容器建议用 `scope`,避免对 div 建立 v-model:
266
-
267
- ```typescript
268
- const schema: VueSchemaNode = {
269
- type: 'div',
270
- model: { path: 'form', scope: true }, // 容器用 scope,不绑定
271
- children: [
272
- {
273
- type: 'input',
274
- model: 'form.user.name' // 明确路径,直接使用
275
- },
276
- {
277
- type: 'input',
278
- model: 'name' // 扁平路径,拼接成 form.name
279
- }
280
- ]
281
- }
282
- ```
283
-
284
- **数组访问语法 `[]`**
285
-
286
- 使用 `[]` 语法可以明确指定数组访问,系统会自动创建数组结构:
287
-
288
- ```typescript
289
- const schema: VueSchemaNode = {
290
- type: 'div',
291
- children: [
292
- {
293
- type: 'input',
294
- model: 'users[0].name' // 使用 [] 语法明确数组访问
295
- },
296
- {
297
- type: 'input',
298
- model: 'users[1].email' // 另一个数组项
299
- }
300
- ]
301
- }
302
-
303
- const { state } = useVario(schema, {
304
- state: {} // 空状态
305
- })
306
-
307
- // ✅ state.users 会被自动创建为数组
308
- // ✅ state.users[0].name 会被自动创建
309
- // ✅ state.users[1].email 会被自动创建
310
- ```
311
-
312
- **嵌套数组和对象**
313
-
314
- 支持复杂的嵌套结构:
315
-
316
- ```typescript
317
- const schema: VueSchemaNode = {
318
- type: 'div',
319
- children: [
320
- {
321
- type: 'input',
322
- model: 'data.users[0].profile.tags[1]' // 混合嵌套
323
- }
324
- ]
325
- }
326
-
327
- // ✅ 自动创建结构:
328
- // state.data (对象)
329
- // → state.data.users (数组)
330
- // → state.data.users[0] (对象)
331
- // → state.data.users[0].profile (对象)
332
- // → state.data.users[0].profile.tags (数组)
333
- // → state.data.users[0].profile.tags[1] (值)
334
- ```
335
-
336
- **循环中的自动路径解析**
337
-
338
- 在循环中,扁平路径会自动拼接循环索引。循环层用 `scope` 提供数组路径,避免对容器做 v-model:
339
-
340
- ```typescript
341
- const schema: VueSchemaNode = {
342
- type: 'div',
343
- model: { path: 'users', scope: true }, // 循环层用 scope,不绑定
344
- loop: {
345
- items: '{{ users }}',
346
- itemKey: 'user'
347
- },
348
- children: [
349
- {
350
- type: 'input',
351
- model: 'name' // 扁平路径 → 自动变成 users[0].name, users[1].name...
352
- },
353
- {
354
- type: 'input',
355
- model: 'age' // 扁平路径 → 自动变成 users[0].age, users[1].age...
356
- }
357
- ]
358
- }
359
-
360
- const { state } = useVario(schema, {
361
- state: {
362
- users: [
363
- { name: 'John', age: 30 },
364
- { name: 'Jane', age: 25 }
365
- ]
366
- }
367
- })
368
-
369
- // ✅ 每个循环项都会正确绑定到对应的数组索引
370
- ```
371
-
372
- **绑定到数组元素本身(`model: "."`)**
373
-
374
- 当希望「子组件输入值」直接作为数组元素(`state.items[0]`、`state.items[1]` 为字符串等原始值),而不是 `items[i].xxx` 的对象的字段时,在循环内使用 **`model: "."`**,表示绑定到当前路径栈对应的路径(即当前循环项在 state 中的位置):
375
-
376
- ```typescript
377
- const schema: VueSchemaNode = {
378
- type: 'div',
379
- loop: { items: 'items', itemKey: 'it' },
380
- model: { path: 'items', scope: true },
381
- children: [
382
- { type: 'input', model: '.' } // 绑定到 items[0]、items[1] 本身
383
- ]
384
- }
385
-
386
- const { state } = useVario(schema, {
387
- state: { items: ['', ''] }
388
- })
389
-
390
- // 第 1 行输入 "a" → state.items[0] === 'a'
391
- // 第 2 行输入 "b" → state.items[1] === 'b'
392
- // state.items 为字符串数组,不是对象数组
393
- ```
394
-
395
- 使用 `model: "."` 时,当前路径栈必须非空(例如在带 `model: { path, scope: true }` 的循环下)才会创建绑定。
396
-
397
- **循环中嵌套的 model 路径栈**
398
-
399
- 循环中可以嵌套多层;每层容器用 `scope` 只提供路径、不绑定:
400
-
401
- ```typescript
402
- const schema: VueSchemaNode = {
403
- type: 'div',
404
- model: { path: 'data', scope: true }, // 容器用 scope
405
- loop: {
406
- items: '{{ data.items }}',
407
- itemKey: 'item'
408
- },
409
- children: [
410
- {
411
- type: 'div',
412
- model: { path: 'item.profile', scope: true }, // 相对循环项压栈,不绑定
413
- children: [
414
- {
415
- type: 'input',
416
- model: 'name' // 扁平路径 → data.items[0].profile.name
417
- }
418
- ]
419
- }
420
- ]
421
- }
422
- ```
423
-
424
- **modelPath 配置选项**
425
-
426
- 仅保留与格式相关的配置(如路径分隔符),路径解析与绑定行为由 model 写法决定,无需开关:
427
-
428
- ```typescript
429
- const { state } = useVario(schema, {
430
- modelPath: {
431
- separator: '.' // 路径分隔符(默认 '.')
432
- },
433
- state: {}
434
- })
435
- ```
436
-
437
- **智能类型推断**
438
-
439
- 系统会根据路径结构自动推断并创建数组或对象:
440
-
441
- - 使用 `[]` 语法或路径中包含数字索引 → 创建数组
442
- - 使用 `.` 语法或字符串键 → 创建对象
443
- - 默认情况下,纯数字段(如 `users.0.name`)会被视为数组索引
444
-
445
- ```typescript
446
- // 自动创建数组
447
- model: 'users[0].name' → state.users = []
448
-
449
- // 自动创建对象
450
- model: 'user.profile.name' → state.user = {}, state.user.profile = {}
451
-
452
- // 混合结构
453
- model: 'data.users[0].profile.tags[1]'
454
- → state.data = {}
455
- → state.data.users = []
456
- → state.data.users[0] = {}
457
- → state.data.users[0].profile = {}
458
- → state.data.users[0].profile.tags = []
459
- ```
460
-
461
- **路径语法与 model 写法总结**
462
-
463
- | 类型 | 示例 | 说明 | 自动创建 |
464
- |------|------|------|---------|
465
- | 扁平路径 | `model: "name"` | 单段路径,自动拼接父级路径栈 | ✅ 对象 |
466
- | 明确路径 | `model: "form.user.name"` | 完整路径,直接使用 | ✅ 对象 |
467
- | 当前栈路径 | `model: "."` | 绑定到当前路径栈(循环中即 `items[0]`、`items[1]` 等元素本身);栈非空时有效 | ✅ 按栈推断 |
468
- | 作用域(不绑定) | `model: { path: "form", scope: true }` | 仅压栈,本节点不绑定;子节点扁平路径拼在 `path` 下 | — |
469
- | 数组访问 `[]` | `model: "users[0].name"` | 明确数组索引访问 | ✅ 数组 |
470
- | 动态索引 `[]` | `model: "users[].name"` | 循环中自动填充索引 | ✅ 数组 |
471
- | 点语法数字 | `model: "users.0.name"` | 纯数字段视为数组索引 | ✅ 数组 |
472
- | 混合嵌套 | `model: "data[0].items[1].value"` | 数组和对象混合 | ✅ 自动推断 |
473
-
474
- **最佳实践**
475
-
476
- 1. **使用 `[]` 语法明确数组访问**:`users[0].name` 比 `users.0.name` 更清晰
477
- 2. **扁平路径用于层级结构**:在父级定义 `model` 后,子级使用扁平路径自动拼接
478
- 3. **明确路径用于跨层级访问**:需要访问非直接父级时使用完整路径
479
- 4. **循环中利用自动索引**:在循环中使用扁平路径,系统会自动添加索引
480
-
481
- ### 表达式系统
482
-
483
- Schema 中支持表达式语法,使用 `{{ }}` 包裹:
484
-
485
- ```typescript
486
- const schema: VueSchemaNode = {
487
- type: 'div',
488
- children: [
489
- {
490
- type: 'div',
491
- children: '{{ firstName + " " + lastName }}' // 字符串拼接
492
- },
493
- {
494
- type: 'div',
495
- children: '{{ count * 2 }}' // 数学运算
496
- },
497
- {
498
- type: 'div',
499
- show: 'count > 10', // 条件显示
500
- children: 'Count is greater than 10'
501
- }
502
- ]
503
- }
504
- ```
505
-
506
- 表达式会在 Vue 的响应式上下文中求值,自动追踪依赖。
507
-
508
- ## Vue 特性集成
509
-
510
- ### Ref 模板引用
511
-
512
- 在 Schema 中声明 `ref`,通过 `useVario` 返回的 `refs` 对象访问组件实例。
513
-
514
- ```typescript
515
- const schema: VueSchemaNode = {
516
- type: 'div',
517
- children: [
518
- {
519
- type: 'ElInput',
520
- ref: 'inputRef', // 声明 ref
521
- props: {
522
- modelValue: '{{ inputValue }}'
523
- }
524
39
  },
525
40
  {
526
41
  type: 'ElButton',
527
42
  events: {
528
43
  click: {
529
44
  type: 'call',
530
- method: 'focusInput'
45
+ method: 'handleClick'
531
46
  }
532
47
  },
533
- children: '聚焦输入框'
534
- }
535
- ]
536
- }
537
-
538
- const { refs, methods } = useVario(schema, {
539
- state: { inputValue: '' },
540
- methods: {
541
- focusInput: () => {
542
- refs.inputRef.value?.focus() // ✅ 访问组件实例
543
- }
544
- }
545
- })
546
- ```
547
-
548
- ### 生命周期钩子
549
-
550
- 在 Schema 中声明生命周期钩子方法名,精确控制每个组件的生命周期。
551
-
552
- ```typescript
553
- const schema: VueSchemaNode = {
554
- type: 'div',
555
- onMounted: 'initData', // 挂载后
556
- onUnmounted: 'cleanup', // 卸载前
557
- onUpdated: 'onUpdate', // 更新后
558
- onBeforeMount: 'beforeInit', // 挂载前
559
- onBeforeUnmount: 'beforeCleanup', // 卸载前
560
- onBeforeUpdate: 'beforeUpdate', // 更新前
561
- children: 'Content'
562
- }
563
-
564
- const { methods } = useVario(schema, {
565
- state: {},
566
- methods: {
567
- initData: ({ state, ctx }) => {
568
- console.log('组件已挂载')
569
- // 初始化逻辑
570
- },
571
- cleanup: ({ state, ctx }) => {
572
- console.log('组件即将卸载')
573
- // 清理逻辑
574
- }
575
- }
576
- })
577
- ```
578
-
579
- **注意**:生命周期钩子映射到 Vue 原生 API,不是重新实现。
580
-
581
- ### Provide/Inject 依赖注入
582
-
583
- 在 Schema 中声明 `provide` 和 `inject`,实现组件间的数据传递。
584
-
585
- ```typescript
586
- // 父组件 Schema
587
- const parentSchema: VueSchemaNode = {
588
- type: 'div',
589
- provide: {
590
- theme: 'dark',
591
- locale: 'currentLocale', // 表达式:从状态读取
592
- apiUrl: 'https://api.example.com' // 静态值
593
- },
594
- children: [
595
- {
596
- type: 'ChildComponent'
48
+ children: '点击'
597
49
  }
598
50
  ]
599
51
  }
600
52
 
601
- // 子组件 Schema
602
- const childSchema: VueSchemaNode = {
603
- type: 'div',
604
- // 数组形式
605
- inject: ['theme', 'locale'],
606
- // 或对象形式
607
- // inject: {
608
- // myTheme: 'theme',
609
- // appLocale: { from: 'locale', default: 'en-US' }
610
- // },
611
- children: [
612
- {
613
- type: 'div',
614
- children: 'Theme: {{ theme }}, Locale: {{ locale }}'
615
- }
616
- ]
617
- }
618
- ```
619
-
620
- ### Teleport 传送
621
-
622
- 将组件传送到指定的 DOM 节点。
623
-
624
- ```typescript
625
- const schema: VueSchemaNode = {
626
- type: 'div',
627
- children: [
628
- {
629
- type: 'ElDialog',
630
- teleport: 'body', // 传送到 body
631
- props: {
632
- modelValue: '{{ dialogVisible }}'
53
+ export default {
54
+ setup() {
55
+ const { vnode, state, methods } = useVario(schema, {
56
+ state: {
57
+ name: ''
633
58
  },
634
- children: '对话框内容'
635
- }
636
- ]
637
- }
638
- ```
639
-
640
- ### Transition 过渡动画
641
-
642
- 为组件添加过渡效果。
643
-
644
- ```typescript
645
- const schema: VueSchemaNode = {
646
- type: 'div',
647
- transition: 'fade', // 简单形式
648
- // 或完整配置
649
- // transition: {
650
- // name: 'fade',
651
- // appear: true,
652
- // mode: 'out-in',
653
- // duration: { enter: 300, leave: 200 }
654
- // },
655
- children: 'Content'
59
+ methods: {
60
+ handleClick: ({ state, ctx }) => {
61
+ console.log('Clicked', state.name)
62
+ }
63
+ }
64
+ })
65
+
66
+ return { vnode, state }
67
+ }
656
68
  }
657
69
  ```
658
70
 
659
- ### Keep-Alive 缓存
71
+ ## 核心特性
660
72
 
661
- 缓存组件状态。
73
+ ### 双向绑定
662
74
 
663
75
  ```typescript
664
- const schema: VueSchemaNode = {
665
- type: 'div',
666
- keepAlive: true,
667
- // 或完整配置
668
- // keepAlive: {
669
- // include: 'ComponentA',
670
- // exclude: 'ComponentB',
671
- // max: 10
672
- // },
673
- children: 'Content'
76
+ {
77
+ type: 'ElInput',
78
+ model: 'user.name' // 自动创建响应式绑定
674
79
  }
675
80
  ```
676
81
 
677
- ## Computed 和 Watch
678
-
679
- **重要**:`computed` 和 `watch` **不在 Schema 中定义**,应该在 Vue 组件中使用原生 API 定义。
680
-
681
- ### Computed 计算属性
82
+ ### 表达式
682
83
 
683
84
  ```typescript
684
- import { computed } from 'vue'
685
-
686
- const schema: VueSchemaNode = {
85
+ {
687
86
  type: 'div',
688
- children: [
689
- {
690
- type: 'div',
691
- children: '{{ fullName }}' // 使用 computed
692
- }
693
- ]
694
- }
695
-
696
- const state = reactive({
697
- firstName: 'John',
698
- lastName: 'Doe'
699
- })
700
-
701
- // ✅ 使用 Vue 原生 computed
702
- const fullName = computed(() => state.firstName + ' ' + state.lastName)
703
-
704
- const { vnode } = useVario(schema, {
705
- state,
706
- computed: {
707
- fullName // 传入 Vue ComputedRef
708
- }
709
- })
710
- ```
711
-
712
- ### Watch 监听器
713
-
714
- ```typescript
715
- import { watch } from 'vue'
716
-
717
- const state = reactive({
718
- count: 0,
719
- user: { name: 'John' }
720
- })
721
-
722
- // ✅ 使用 Vue 原生 watch
723
- watch(() => state.count, (newVal, oldVal) => {
724
- console.log('Count changed:', newVal, '->', oldVal)
725
- })
726
-
727
- watch(() => state.user.name, (newVal) => {
728
- console.log('User name changed:', newVal)
729
- }, { immediate: true, deep: true })
730
-
731
- const { vnode } = useVario(schema, {
732
- state
733
- })
734
- ```
735
-
736
- **为什么不在 Schema 中定义?**
737
-
738
- - 避免重新实现 Vue 的响应式系统
739
- - 利用 Vue 的编译优化
740
- - 保持与 Vue 生态的一致性
741
- - 更好的类型推导和 IDE 支持
742
-
743
- ## 控制流
744
-
745
- ### 条件渲染
746
-
747
- ```typescript
748
- const schema: VueSchemaNode = {
749
- type: 'div',
750
- cond: 'isVisible', // 条件渲染(v-if)
751
- // 或
752
- show: 'isVisible', // 条件显示(v-show)
753
- children: 'Content'
87
+ children: '{{ firstName + " " + lastName }}',
88
+ show: 'count > 10'
754
89
  }
755
90
  ```
756
91
 
757
92
  ### 循环渲染
758
93
 
759
94
  ```typescript
760
- const schema: VueSchemaNode = {
95
+ {
761
96
  type: 'div',
762
97
  loop: {
763
- items: '{{ userList }}', // 数据源
764
- itemKey: 'item', // 项变量名
765
- indexKey: 'index' // 索引变量名(可选)
766
- },
767
- children: [
768
- {
769
- type: 'div',
770
- children: '{{ index + 1 }}. {{ item.name }}'
771
- }
772
- ]
773
- }
774
- ```
775
-
776
- ## 事件处理
777
-
778
- ```typescript
779
- const schema: VueSchemaNode = {
780
- type: 'ElButton',
781
- events: {
782
- click: {
783
- type: 'call',
784
- method: 'handleClick'
785
- }
98
+ items: '{{ userList }}',
99
+ itemKey: 'item'
786
100
  },
787
- children: '点击'
101
+ children: '{{ item.name }}'
788
102
  }
789
-
790
- const { methods } = useVario(schema, {
791
- state: {},
792
- methods: {
793
- handleClick: ({ state, ctx, event }) => {
794
- console.log('Button clicked', event)
795
- }
796
- }
797
- })
798
103
  ```
799
104
 
800
- ## API 参考
105
+ ### Vue 特性
801
106
 
802
- ### useVario
803
-
804
- ```typescript
805
- function useVario<TState extends Record<string, unknown>>(
806
- schema: Schema<TState> | (() => Schema<TState>) | ComputedRef<Schema<TState>>,
807
- options?: UseVarioOptions<TState>
808
- ): UseVarioResult<TState>
809
- ```
810
-
811
- #### Options
812
-
813
- ```typescript
814
- interface UseVarioOptions<TState> {
815
- /** 初始状态(会自动包裹为响应式对象) */
816
- state?: TState
817
-
818
- /** 计算属性(传入 Vue ComputedRef) */
819
- computed?: Record<string, ((state: TState) => any) | ComputedRef<any>>
820
-
821
- /** 方法定义 */
822
- methods?: Record<string, (context: MethodContext<TState>) => any>
823
-
824
- /** 表达式选项 */
825
- exprOptions?: ExpressionOptions
826
-
827
- /** 自定义 model 绑定配置 */
828
- modelBindings?: Record<string, ModelConfig>
829
-
830
- /** Model 路径解析配置(可选,仅与格式相关如 separator) */
831
- modelPath?: {
832
- /** 路径分隔符(默认 '.') */
833
- separator?: string
834
- }
835
-
836
- /** 渲染器选项 */
837
- rendererOptions?: VueRendererOptions
838
-
839
- /** 错误边界配置 */
840
- errorBoundary?: {
841
- enabled?: boolean
842
- fallback?: (error: Error) => VNode
843
- }
844
-
845
- /** 事件发射器 */
846
- onEmit?: (event: string, data?: unknown) => void
847
- }
848
- ```
849
-
850
- #### 返回值
851
-
852
- ```typescript
853
- interface UseVarioResult<TState> {
854
- /** 渲染的 VNode */
855
- vnode: Ref<VNode | null>
856
-
857
- /** 响应式状态(自动包裹) */
858
- state: TState
859
-
860
- /** 运行时上下文 */
861
- ctx: Ref<RuntimeContext<TState>>
862
-
863
- /** 模板引用集合 */
864
- refs: Record<string, Ref<any>>
865
-
866
- /** 当前错误(如果有) */
867
- error: Ref<Error | null>
868
-
869
- /** 手动触发重新渲染 */
870
- retry: () => void
871
- }
872
- ```
873
-
874
- ## 性能优化
875
-
876
- ### 大规模渲染
877
-
878
- - **组件解析缓存**:相同类型的组件只解析一次
879
- - **静态属性缓存**:静态属性会被缓存,避免重复计算
880
- - **事件处理器缓存**:事件处理器会被缓存,避免重复创建
881
- - **循环上下文池**:复用循环上下文对象,减少内存分配
882
-
883
- ### 响应式优化
884
-
885
- - **深度比较**:状态同步时进行深度比较,避免不必要的更新
886
- - **路径追踪**:使用路径追踪避免循环同步
887
- - **批量更新**:使用 `nextTick` 批量处理状态变化
888
-
889
- ### 最佳实践
890
-
891
- 1. **使用虚拟滚动**:对于大型列表(> 1000 项),使用虚拟滚动组件
892
- 2. **静态节点标记**:对于不会变化的节点,考虑使用静态提升
893
- 3. **合理使用 computed**:在组件层面定义 computed,利用 Vue 的缓存机制
894
- 4. **避免深层嵌套**:虽然支持深层嵌套,但建议控制在 3-4 层以内
895
-
896
- ## 类型支持
897
-
898
- ```typescript
899
- import type { VueSchemaNode } from '@vario/vue'
900
-
901
- // VueSchemaNode 扩展了 SchemaNode,添加了 Vue 特有属性
902
- const schema: VueSchemaNode = {
903
- type: 'div',
904
- ref: 'container',
905
- onMounted: 'init',
906
- // ... 其他属性
907
- }
908
- ```
107
+ - **Ref 模板引用**:`ref: 'inputRef'`
108
+ - **生命周期**:`onMounted: 'initData'`
109
+ - **Provide/Inject**:`provide: { theme: 'dark' }`
110
+ - **Teleport**:`teleport: 'body'`
909
111
 
910
- ## 示例
112
+ ## 优势
911
113
 
912
- 查看 `@play/src/examples/` 目录下的完整示例。
114
+ - **类型推导**:完整的 TypeScript 类型支持
115
+ - ✅ **性能优化**:组件解析缓存、静态属性缓存、循环上下文池
116
+ - ✅ **自动同步**:状态与运行时上下文双向同步
117
+ - ✅ **Vue 原生**:computed、watch 使用 Vue 原生 API
913
118
 
914
119
  ## 许可证
915
120