在 Svelte 5 项目中安装并使用 Piying-View 渲染第一个表单。
🚀 想直接看效果? 直接拉模板仓库:https://github.com/piying-org/piying-view-svelte-template
git clone https://github.com/piying-org/piying-view-svelte-template
cd piying-view-svelte-template
npm install && npm run dev
💡 先分清两种模式:本文演示的是自动模式(<PiyingView> + Schema 全自动渲染)。另一种手动模式(convertToField + <PiyingField> 手动绑定)见 两种使用模式。
pnpm add valibot @piying/view-core @piying/view-svelte
Svelte 版用 runes($state)实现 CVA,组件通过 export { cva } 把 CVA 暴露给 Piying-View 注册。
<!-- src/lib/piying/input-text.svelte -->
<script lang="ts">
import { useControlValueAccessor } from '@piying/view-svelte';
const { cva, cvaa } = useControlValueAccessor();
// 关键:导出 cva,供库内部注册
export { cva };
</script>
<input
class="input"
type="text"
bind:value={() => cvaa.value, (v) => cvaa.valueChange(v)}
disabled={cvaa.disabled}
onblur={cvaa.touchedChange}
/>
cvaa 提供:
| 成员 |
类型 |
说明 |
value |
$state getter |
当前值 |
disabled |
$state getter |
禁用状态 |
valueChange(v) |
(v) => void |
更新值并触发变更 |
touchedChange() |
() => void |
标记为已触碰 |
照这个模式再写两个控件:
<!-- src/lib/piying/input-number.svelte -->
<script lang="ts">
import { useControlValueAccessor } from '@piying/view-svelte';
const { cva, cvaa } = useControlValueAccessor();
export { cva };
</script>
<input
class="input"
type="number"
bind:value={() => (cvaa.value == null ? '' : String(cvaa.value)), (v) => cvaa.valueChange(v === '' ? undefined : Number(v))}
disabled={cvaa.disabled}
onblur={cvaa.touchedChange}
/>
<!-- src/lib/piying/input-checkbox.svelte -->
<script lang="ts">
import { useControlValueAccessor } from '@piying/view-svelte';
const { cva, cvaa } = useControlValueAccessor();
export { cva };
</script>
<input
class="checkbox"
type="checkbox"
bind:checked={() => !!cvaa.value, (v) => cvaa.valueChange(v)}
disabled={cvaa.disabled}
onblur={cvaa.touchedChange}
/>
Svelte 包不提供 use-*Model 系列 Hook,直接用 bind: 的 getter/setter 形式绑定即可。
包装器(Wrapper)负责在控件外面套一层标签。Svelte 通过 getContext 拿到当前字段,读取 props['title']。
<!-- src/lib/piying/wrapper/label-wrapper.svelte -->
<script lang="ts">
import { getContext } from 'svelte';
import { PI_VIEW_FIELD_TOKEN, signalToState } from '@piying/view-svelte';
let { children } = $props();
const field = getContext<PI_VIEW_FIELD_TOKEN>(PI_VIEW_FIELD_TOKEN);
const fieldProps = signalToState(() => field().props());
</script>
<div class="flex items-center gap-2">
{#if fieldProps()?.['title']}
<span class="label">{fieldProps()?.['title']}</span>
{/if}
{@render children()}
</div>
Wrappers 的完整写法见 Wrappers 包装器。
fieldConfig 是一张「类型名 → 组件」的注册表,Piying-View 按 Schema 推导出的类型名来这里查找渲染组件。
// src/lib/piying/define.ts
import { actions, lazyMark } from '@piying/view-core';
import { PiyingViewGroup, type PiViewConfig } from '@piying/view-svelte';
import InputText from './input-text.svelte';
import InputNumber from './input-number.svelte';
import InputCheckbox from './input-checkbox.svelte';
import LabelWrapper from './wrapper/label-wrapper.svelte';
export const fieldConfig = {
types: {
string: { type: InputText, actions: [actions.wrappers.set(['label'])] },
number: { type: InputNumber, actions: [actions.wrappers.set(['label'])] },
boolean: { type: InputCheckbox, actions: [actions.wrappers.set(['label'])] },
// 对象 / 数组等容器类型,用内置的组容器
object: { type: PiyingViewGroup },
array: { type: PiyingViewGroup },
},
wrappers: {
label: { type: LabelWrapper },
},
} as PiViewConfig;
Svelte 的懒加载需要用 lazyMark() 包一层:type: lazyMark(() => import('./xxx.svelte').then((m) => m.default)),否则动态 import 的组件无法被正确识别。
<!-- src/lib/page/PiyingDemo.svelte -->
<script lang="ts">
import * as v from 'valibot';
import { PiyingView } from '@piying/view-svelte';
import { fieldConfig } from '../piying/define';
const schema = v.object({
name: v.pipe(v.string(), v.minLength(2, '名称至少 2 个字符'), v.title('姓名')),
age: v.pipe(v.number(), v.minValue(18, '必须年满 18 岁'), v.title('年龄')),
email: v.pipe(v.optional(v.string()), v.title('邮箱')),
});
const options = {
fieldGlobalConfig: fieldConfig,
};
let model = $state<Record<string, any>>({});
function modelChange(value: Record<string, any>) {
model = value;
}
</script>
<PiyingView {schema} {options} {model} {modelChange} />
<pre>{JSON.stringify(model, null, 2)}</pre>
<PiyingView> 接收四个属性:
| 属性 |
说明 |
{schema} |
Valibot Schema,定义字段和验证规则 |
{model} |
传入的模型值 |
{modelChange} |
模型变更回调(仅在无验证错误时触发) |
{options} |
Options 配置(fieldGlobalConfig 等) |
model 不是 $bindable() prop,不能写 bind:model;需要在 modelChange 里自己回写本地状态。
打开浏览器,你会看到 name / age / email 三个字段的表单。输入数据时 model 会同步更新;校验不通过时不会向 model 写出错误值。