npm package: @form-renderer/adapter-vue3
Description: Vue3 adapter for FormEngine
Latest version: 1.0.0-alpha.12

# FormAdapter

FormAdapter 是基于 FormEngine 的 Vue3 表单渲染适配器，提供了完整的表单渲染、交互和组件集成能力。它将 FormEngine 的声明式 Schema 转换为可视化的 Vue 组件，并提供响应式的数据绑定和事件处理。

## 项目结构
详见 [项目结构文档](./docs/PROJECT_STRUCTURE.md)。

## 核心特性

### 1. 响应式引擎集成
- **Vue3 响应式系统深度集成**
- 基于 `shallowRef` 的性能优化
- **不可变数据流，确保追踪性**
- 自动同步 FormEngine 和 Vue 状态

### 2. 组件注册系统
- 灵活的组件定义机制
- 支持单个注册、批量注册和预设注册
- 内置值转换器和事件映射
- 支持自定义渲染逻辑

### 3. 事件处理系统
- 统一的事件处理接口
- 可选的批量更新优化
- 完整的事件生命周期管理
- 错误处理和恢复机制

### 4. UI 框架集成
- 支持多种 UI 框架（Element Plus、Ant Design Vue 等）
- 自动转换校验规则（validators → rules）
- FormItem 自动包裹
- 主题和样式定制

### 5. 组合式 API
- `useFormAdapter` - 编程式表单管理
- `useFieldComponent` - 字段组件开发
- 完整的 TypeScript 类型支持
- 响应式状态和方法

### 6. 性能优化
- 智能批量更新调度
- 浅比较优化渲染
- 按需加载组件
- 结构共享机制

## 架构设计

FormAdapter 采用分层架构：

```
FormAdapter
├── FormAdapter.vue         # 主组件（入口）
├── SchemaRenderer.vue      # Schema 渲染器
├── Core 层                 # 核心模块
│   ├── ReactiveEngine      # 响应式引擎
│   ├── ComponentRegistry   # 组件注册表
│   ├── EventHandler        # 事件处理器
│   └── UpdateScheduler     # 更新调度器
├── Composables 层          # 组合式函数
│   ├── useFormAdapter      # 表单适配器
│   └── useFieldComponent   # 字段组件
├── Components 层           # 容器组件
│   ├── FormContainer       # 表单容器
│   ├── LayoutContainer     # 布局容器
│   └── ListContainer       # 列表容器
└── Presets 层             # 预设集成
    ├── ElementPlusPreset  # Element Plus 预设
    └── ExamplePreset      # 示例预设
```

## 设计原则

1. **渐进增强** - 从基础功能到高级特性，按需使用
2. **框架无关** - 核心逻辑与 UI 框架解耦
3. **类型安全** - 完整的 TypeScript 类型定义
4. **性能优先** - 批处理、浅比较、按需更新
5. **开发友好** - 清晰的 API、完善的文档、丰富的示例
6. **扩展性强** - 易于扩展新组件和预设

## 适用场景

- 复杂动态表单渲染
- 低代码/无代码平台
- 配置化表单系统
- 表单设计器/构建器
- 多 UI 框架适配

## 快速开始

### 安装

```bash
npm install @form-renderer/adapter-vue3 @form-renderer/engine vue
```

### 基础使用

```vue
<template>
  <FormAdapter
    :schema="schema"
    v-model:model="formData"
    :components="ElementPlusPreset"
    @submit="handleSubmit"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { FormAdapter } from '@form-renderer/adapter-vue3'
import { ElementPlusPreset } from '@form-renderer/adapter-vue3/presets'

const schema = {
  type: 'form',
  properties: {
    name: {
      type: 'field',
      component: 'Input',
      required: true,
      formItemProps: {
        label: '姓名'
      }
    },
    age: {
      type: 'field',
      component: 'InputNumber',
      required: true,
      formItemProps: {
        label: '年龄'
      }
    }
  }
}

const formData = ref({
  name: '',
  age: undefined
})

const handleSubmit = async (data) => {
  console.log('提交数据:', data)
}
</script>
```

### 使用组合式 API

```typescript
import { useFormAdapter } from '@form-renderer/adapter-vue3'
import { ElementPlusPreset } from '@form-renderer/adapter-vue3/presets'

const {
  renderSchema,
  model,
  updateValue,
  validate,
  submit,
  reset
} = useFormAdapter({
  schema: mySchema,
  model: { name: '', age: undefined },
  components: ElementPlusPreset,
  onSubmit: async (data) => {
    await api.submit(data)
  }
})

// 更新值
updateValue('name', 'John')

// 校验表单
const result = await validate()

// 提交表单
await submit()

// 重置表单
reset()
```

## 核心概念

### 1. ReactiveEngine - 响应式引擎

ReactiveEngine 是 FormAdapter 的核心，负责将 FormEngine 与 Vue3 响应式系统集成。

**特性：**
- 基于 `shallowRef` 的浅响应式
- 自动同步 FormEngine 的状态变化
- 支持值变化和结构变化事件
- 可选的更新调度优化

**使用：**

```typescript
import { createReactiveEngine } from '@form-renderer/adapter-vue3'

const engine = createReactiveEngine({
  schema: mySchema,
  model: myModel,
  enableUpdateScheduler: true
})

// 获取响应式 renderSchema（只读）
const renderSchema = engine.getRenderSchema()

// 获取响应式 model（只读）
const model = engine.getModel()

// 更新值
engine.updateValue('name', 'John')

// 销毁引擎
engine.destroy()
```

### 2. ComponentRegistry - 组件注册表

ComponentRegistry 管理所有可用的组件定义。

**组件定义结构：**

```typescript
interface ComponentDefinition {
  name: string                    // 组件名称
  component: Component            // Vue 组件
  type: ComponentType             // 组件类型
  defaultProps?: Record<string, any>
  valueTransformer?: ValueTransformer
  eventMapping?: EventMapping
  needFormItem?: boolean
  customRender?: (props) => VNode
}
```

**使用：**

```typescript
import { createComponentRegistry } from '@form-renderer/adapter-vue3'

const registry = createComponentRegistry()

// 注册单个组件
registry.register({
  name: 'Input',
  component: ElInput,
  type: 'field',
  eventMapping: {
    onChange: 'update:modelValue'
  }
})

// 批量注册
registry.registerBatch([
  { name: 'Input', component: ElInput, type: 'field' },
  { name: 'Select', component: ElSelect, type: 'field' }
])

// 注册预设
registry.registerPreset(ElementPlusPreset)

// 获取组件
const inputDef = registry.get('Input')
```

### 3. EventHandler - 事件处理器

EventHandler 负责处理所有用户交互事件。

**事件类型：**
- 字段值变化（change）
- 字段聚焦/失焦（focus/blur）
- 列表操作（add/remove/move）

**使用：**

```typescript
import { createEventHandler } from '@form-renderer/adapter-vue3'

const handler = createEventHandler(engine, registry, {
  enableBatch: true,
  batchDelay: 16
})

// 处理字段变化
handler.handleFieldChange('name', 'John', 'Input')

// 处理列表添加
handler.handleListAdd('items', { name: '', price: 0 })

// 处理列表删除
handler.handleListRemove('items', 0)

// 立即刷新批量更新
handler.flush()
```

**事件处理层次：**

FormAdapter 支持两种层次的事件处理：

1. **核心事件**（由 EventHandler 处理）：
   - `onChange`: 字段值变化，会更新 model
   - `onInput`: 输入事件，只更新显示值
   - `onFocus`: 字段聚焦
   - `onBlur`: 字段失焦

2. **字段级自定义事件**（直接绑定到组件）：
   - `onKeydown`、`onKeyup`: 键盘事件
   - `onClick`、`onMouseenter`: 鼠标事件
   - 以及任何组件支持的原生事件

**使用自定义事件：**

```typescript
const schema = {
  fields: [
    {
      path: 'username',
      component: 'input',
      componentProps: {
        // 在 componentProps 中定义事件处理器
        onKeydown: (e: KeyboardEvent) => {
          if (e.key === 'Enter') {
            // 处理回车
          }
        },
        onFocus: () => {
          console.log('字段获得焦点')
        }
      }
    }
  ]
}
```

详见：[自定义事件处理指南](./docs/CUSTOM_EVENTS.md)

### 4. 组件预设（ComponentPreset）

组件预设是一组预定义的组件配置，方便快速集成 UI 框架。

**预设结构：**

```typescript
interface ComponentPreset {
  name: string                    // 预设名称
  components: ComponentDefinition[] // 组件列表
  formItem?: Component            // FormItem 组件
  ruleConverter?: RuleConverter   // 校验规则转换器
  theme?: ThemeConfig             // 主题配置
  setup?: () => void              // 初始化函数
}
```

**示例：**

```typescript
export const MyPreset: ComponentPreset = {
  name: 'my-preset',
  components: [
    {
      name: 'Input',
      component: MyInput,
      type: 'field',
      eventMapping: {
        onChange: 'update:modelValue'
      }
    }
  ],
  formItem: MyFormItem,
  ruleConverter: (node, computed, context) => {
    const rules = []
    if (computed.required) {
      rules.push({ required: true, message: '必填' })
    }
    return rules
  }
}
```

## 组件开发

### 字段组件（Field Component）

字段组件是最基础的组件类型，用于渲染表单字段。

**接口规范：**

```typescript
interface FieldComponentProps {
  modelValue: any          // v-model 绑定值
  disabled?: boolean       // 禁用状态
  readonly?: boolean       // 只读状态
  placeholder?: string     // 占位符
  [key: string]: any       // 其他属性
}

interface FieldComponentEmits {
  'update:modelValue': (value: any) => void
  focus?: (event: FocusEvent) => void
  blur?: (event: FocusEvent) => void
  change?: (value: any) => void
}
```

**示例：**

```vue
<template>
  <input
    :value="modelValue"
    :disabled="disabled"
    :readonly="readonly"
    :placeholder="placeholder"
    @input="handleInput"
    @focus="handleFocus"
    @blur="handleBlur"
  />
</template>

<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'

const props = defineProps<{
  modelValue: any
  disabled?: boolean
  readonly?: boolean
  placeholder?: string
}>()

const emit = defineEmits<{
  'update:modelValue': [value: any]
  focus: [event: FocusEvent]
  blur: [event: FocusEvent]
}>()

const handleInput = (e: Event) => {
  emit('update:modelValue', (e.target as HTMLInputElement).value)
}

const handleFocus = (e: FocusEvent) => {
  emit('focus', e)
}

const handleBlur = (e: FocusEvent) => {
  emit('blur', e)
}
</script>
```

### 布局组件（Layout Component）

布局组件用于组织和排列表单字段。

```vue
<template>
  <div class="layout-container">
    <h3 v-if="title">{{ title }}</h3>
    <slot />
  </div>
</template>

<script setup lang="ts">
defineProps<{
  title?: string
}>()
</script>
```

### 列表组件（List Component）

列表组件用于渲染动态列表。

```vue
<template>
  <div class="list-container">
    <div v-for="(row, index) in rows" :key="index" class="list-item">
      <slot :row="row" :index="index" />
      <button @click="emit('remove', index)">删除</button>
    </div>
    <button @click="emit('add')">添加</button>
  </div>
</template>

<script setup lang="ts">
defineProps<{
  rows: any[]
}>()

const emit = defineEmits<{
  add: []
  remove: [index: number]
}>()
</script>
```

## 值转换器（ValueTransformer）

值转换器用于在组件值和引擎值之间进行转换。

**使用场景：**
- 日期组件：`Date` ↔ `string`
- 数字组件：`number` ↔ `string`
- 多选组件：`string[]` ↔ `string`（逗号分隔）

**示例：**

```typescript
const dateTransformer: ValueTransformer = {
  // 引擎值 → 组件值
  toComponent: (engineValue: string) => {
    return engineValue ? new Date(engineValue) : null
  },
  // 组件值 → 引擎值
  fromComponent: (componentValue: Date | null) => {
    return componentValue ? componentValue.toISOString() : ''
  }
}

// 注册时使用
registry.register({
  name: 'DatePicker',
  component: ElDatePicker,
  type: 'field',
  valueTransformer: dateTransformer
})
```

## 校验规则转换（RuleConverter）

RuleConverter 将 FormEngine 的 validators 转换为 UI 框架的 rules 格式。

**示例（Element Plus）：**

```typescript
const ruleConverter: RuleConverter = (node, computed, context) => {
  const rules = []

  // 必填校验
  if (computed.required) {
    rules.push({
      required: true,
      message: `${node.formItemProps?.label || '该字段'}为必填项`,
      trigger: 'blur'
    })
  }

  // 自定义校验器
  if (node.validators && Array.isArray(node.validators)) {
    node.validators.forEach((validator) => {
      rules.push({
        validator: async (rule, value, callback) => {
          try {
            const ctx = {
              path: node.path,
              getSchema: context.engine.getEngine().getSchema,
              getValue: context.engine.getEngine().getValue,
              getCurRowValue: () => ({}),
              getCurRowIndex: () => -1
            }
            const result = await validator(value, ctx)
            
            if (result === false || typeof result === 'string') {
              callback(new Error(typeof result === 'string' ? result : '校验失败'))
            } else {
              callback()
            }
          } catch (error) {
            callback(error)
          }
        },
        trigger: 'blur'
      })
    })
  }

  return rules
}
```

## FormItem 集成

FormItem 组件用于包裹字段组件，提供标签、错误信息等。

**要求：**
- 接收 `label`、`required`、`error` 等 props
- 提供默认插槽用于渲染字段组件
- 支持校验规则（如果使用 ruleConverter）

**示例（Element Plus）：**

```vue
<template>
  <el-form-item
    :label="label"
    :required="required"
    :prop="prop"
    :rules="rules"
    :error="error"
  >
    <slot />
  </el-form-item>
</template>
```

## 更新调度（UpdateScheduler）

UpdateScheduler 用于优化批量更新性能。

**特性：**
- 使用 `requestAnimationFrame` 调度
- 合并多个连续更新
- 避免不必要的渲染

**使用：**

```typescript
// 创建时启用
const engine = createReactiveEngine({
  schema: mySchema,
  model: myModel,
  enableUpdateScheduler: true
})

// 多次更新会自动合并
engine.updateValue('name', 'John')
engine.updateValue('age', 25)
engine.updateValue('city', 'Beijing')
// ↑ 这三次更新会在下一帧合并为一次批量更新

// 立即刷新
engine.flush()
```

## 性能优化

### 1. 使用 shallowRef

FormAdapter 使用 `shallowRef` 而非 `ref`，避免深度响应式的性能开销。

```typescript
// ✅ 好
const renderSchema = shallowRef(engine.getRenderSchema())

// ❌ 差
const renderSchema = ref(engine.getRenderSchema())
```

### 2. 启用更新调度

```typescript
const engine = createReactiveEngine({
  schema,
  model,
  enableUpdateScheduler: true  // ✅ 启用批量更新
})
```

### 3. 启用事件批处理

```typescript
const handler = createEventHandler(engine, registry, {
  enableBatch: true,  // ✅ 启用批处理
  batchDelay: 16
})
```

### 4. 避免不必要的计算属性

```typescript
// ✅ 好：浅比较
const renderSchema = computed(() => engine.getRenderSchema().value)

// ❌ 差：深比较
const renderSchema = computed(() => JSON.parse(JSON.stringify(engine.getRenderSchema().value)))
```

### 5. 使用组件懒加载

```typescript
const InputComponent = defineAsyncComponent(() => import('./MyInput.vue'))

registry.register({
  name: 'Input',
  component: InputComponent,
  type: 'field'
})
```

## 调试

### 启用日志

```typescript
// 在 FormAdapter 组件中查看日志
console.log('RenderSchema:', renderSchema.value)
console.log('Model:', model.value)
console.log('Engine:', engine.value)
```

### 使用 Vue Devtools

安装 Vue Devtools 浏览器扩展，可以查看：
- 组件树
- 响应式数据
- 事件触发

### 性能分析

```typescript
// 使用 Vue 3 的性能 API
import { startMeasure, stopMeasure } from 'vue'

startMeasure('form-render')
// ... 渲染代码
stopMeasure('form-render')
```

## API 文档
详见 [API 文档](./docs/API.md)。

## 完整示例

### 动态表单

```vue
<template>
  <FormAdapter
    :schema="schema"
    v-model:model="formData"
    :components="ElementPlusPreset"
    @change="handleChange"
    @submit="handleSubmit"
    ref="formRef"
  />
  
  <div class="actions">
    <button @click="handleValidate">校验</button>
    <button @click="handleReset">重置</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { FormAdapter } from '@form-renderer/adapter-vue3'
import { ElementPlusPreset } from '@form-renderer/adapter-vue3/presets'

const formRef = ref()

const schema = {
  type: 'form',
  properties: {
    userType: {
      type: 'field',
      component: 'Select',
      formItemProps: { label: '用户类型' },
      componentProps: {
        options: [
          { label: '个人', value: 'personal' },
          { label: '企业', value: 'company' }
        ]
      }
    },
    personalInfo: {
      type: 'layout',
      ifShow: (ctx) => ctx.getValue('userType') === 'personal',
      properties: {
        name: {
          type: 'field',
          component: 'Input',
          required: true,
          formItemProps: { label: '姓名' }
        },
        idCard: {
          type: 'field',
          component: 'Input',
          required: true,
          formItemProps: { label: '身份证号' },
          validators: [
            (value) => {
              if (value && value.length !== 18) {
                return '身份证号必须是18位'
              }
            }
          ]
        }
      }
    },
    companyInfo: {
      type: 'layout',
      ifShow: (ctx) => ctx.getValue('userType') === 'company',
      properties: {
        companyName: {
          type: 'field',
          component: 'Input',
          required: true,
          formItemProps: { label: '企业名称' }
        },
        creditCode: {
          type: 'field',
          component: 'Input',
          required: true,
          formItemProps: { label: '统一社会信用代码' }
        }
      }
    },
    items: {
      type: 'list',
      items: {
        name: {
          type: 'field',
          component: 'Input',
          required: true,
          formItemProps: { label: '商品名称' }
        },
        price: {
          type: 'field',
          component: 'InputNumber',
          required: true,
          formItemProps: { label: '价格' },
          validators: [
            (value) => {
              if (value <= 0) return '价格必须大于0'
            }
          ]
        },
        quantity: {
          type: 'field',
          component: 'InputNumber',
          required: true,
          formItemProps: { label: '数量' }
        },
        total: {
          type: 'field',
          component: 'InputNumber',
          readonly: true,
          formItemProps: { label: '小计' },
          subscribes: {
            '.price': (ctx) => {
              const row = ctx.getCurRowValue()
              ctx.updateSelf((row.price || 0) * (row.quantity || 0))
            },
            '.quantity': (ctx) => {
              const row = ctx.getCurRowValue()
              ctx.updateSelf((row.price || 0) * (row.quantity || 0))
            }
          }
        }
      }
    }
  }
}

const formData = ref({
  userType: '',
  items: []
})

const handleChange = (event) => {
  console.log('值变化:', event)
}

const handleValidate = async () => {
  const result = await formRef.value.validate()
  console.log('校验结果:', result)
}

const handleSubmit = async (data) => {
  console.log('提交数据:', data)
  // 调用 API
  await api.submitForm(data)
}

const handleReset = () => {
  formRef.value.reset()
}
</script>
```

## 常见问题

### 1. 为什么 v-model 不更新？

确保 FormAdapter 使用了 `v-model:model` 而不是 `:model`：

```vue
<!-- ✅ 正确 -->
<FormAdapter v-model:model="formData" :schema="schema" />

<!-- ❌ 错误 -->
<FormAdapter :model="formData" :schema="schema" />
```

### 2. 组件找不到？

确保组件已注册：

```typescript
// 检查组件是否已注册
const registry = formRef.value.getRegistry()
console.log(registry.has('Input'))  // 应该返回 true
```

### 3. 校验不生效？

检查以下几点：
- 是否正确配置了 `required` 或 `validators`
- 是否使用了 `ruleConverter`（UI 框架校验）
- 字段是否被隐藏（`ifShow: false`）或禁用（`disabled: true`）

### 4. 性能问题？

尝试以下优化：
- 启用 `enableUpdateScheduler`
- 启用 `enableBatch`
- 使用组件懒加载
- 减少深度响应式

### 5. 如何调试事件？

在 FormAdapter 组件上监听所有事件：

```vue
<FormAdapter
  @change="console.log('change', $event)"
  @field-blur="console.log('blur', $event)"
  @field-focus="console.log('focus', $event)"
  @list-change="console.log('list', $event)"
  @validate="console.log('validate', $event)"
/>
```

## 最佳实践

### 1. 组件注册

**推荐：使用预设**

```typescript
// ✅ 好
import { ElementPlusPreset } from '@form-renderer/adapter-vue3/presets'

<FormAdapter :components="ElementPlusPreset" />
```

**不推荐：手动注册每个组件**

```typescript
// ❌ 繁琐
const components = [
  { name: 'Input', component: ElInput, type: 'field' },
  { name: 'Select', component: ElSelect, type: 'field' },
  // ... 很多组件
]
```

### 2. 表单数据管理

**推荐：使用 ref**

```typescript
// ✅ 好
const formData = ref({ name: '', age: 0 })

<FormAdapter v-model:model="formData" />
```

**不推荐：使用 reactive**

```typescript
// ⚠️ 可能导致类型问题
const formData = reactive({ name: '', age: 0 })
```

### 3. 事件处理

**推荐：使用具名回调**

```vue
<template>
  <FormAdapter
    @change="handleChange"
    @submit="handleSubmit"
  />
</template>

<script setup>
const handleChange = (event) => {
  // 处理变化
}

const handleSubmit = async (data) => {
  // 提交逻辑
}
</script>
```

### 4. Schema 定义

**推荐：提取为单独的文件**

```typescript
// schemas/user-form.ts
export const userFormSchema = {
  type: 'form',
  properties: {
    // ...
  }
}

// 使用
import { userFormSchema } from './schemas/user-form'
```

## 迁移指南

### 从原生表单迁移

```vue
<!-- 原生表单 -->
<form @submit="handleSubmit">
  <div>
    <label>姓名</label>
    <input v-model="formData.name" required />
  </div>
  <div>
    <label>年龄</label>
    <input v-model.number="formData.age" type="number" required />
  </div>
  <button type="submit">提交</button>
</form>

<!-- 迁移到 FormAdapter -->
<FormAdapter
  :schema="{
    type: 'form',
    properties: {
      name: {
        type: 'field',
        component: 'Input',
        required: true,
        formItemProps: { label: '姓名' }
      },
      age: {
        type: 'field',
        component: 'InputNumber',
        required: true,
        formItemProps: { label: '年龄' }
      }
    }
  }"
  v-model:model="formData"
  @submit="handleSubmit"
/>
```

## 扩展阅读

- [项目结构](./docs/PROJECT_STRUCTURE.md)
- [API 文档](./docs/API.md)
- [组件预设开发指南](./docs/COMPONENT_PRESET.md)
- [响应式引擎详解](./docs/REACTIVE_ENGINE.md)
- [FormEngine 文档](../Engine/README.md)

## License

MIT