Form
用法
使用 Form 组件可以通过 Valibot, Zod, Yup, Joi, Superstruct 等验证库或你自己的验证逻辑来验证表单数据。
它与 FormField 组件配合使用,可自动显示表单元素周围的错误消息。
模式验证 (Schema Validation)
它需要两个 props:
state- 一个包含表单状态的响应式对象。schema- 任何 Standard Schema 或来自 Yup, Joi 或 Superstruct 的模式。
<script setup lang="ts">
import * as v from 'valibot'
import type { FormSubmitEvent } from '@nuxt/ui'
const schema = v.object({
email: v.pipe(v.string(), v.email('Invalid email')),
password: v.pipe(v.string(), v.minLength(8, 'Must be at least 8 characters'))
})
type Schema = v.InferOutput<typeof schema>
const state = reactive({
email: '',
password: ''
})
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}
</script>
<template>
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
<UFormField label="Email" name="email">
<UInput v-model="state.email" />
</UFormField>
<UFormField label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>
<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
const schema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Must be at least 8 characters')
})
type Schema = z.output<typeof schema>
const state = reactive<Partial<Schema>>({
email: undefined,
password: undefined
})
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}
</script>
<template>
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
<UFormField label="Email" name="email">
<UInput v-model="state.email" />
</UFormField>
<UFormField label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>
<script setup lang="ts">
import { object, string } from 'yup'
import type { InferType } from 'yup'
import type { FormSubmitEvent } from '@nuxt/ui'
const schema = object({
email: string().email('Invalid email').required('Required'),
password: string()
.min(8, 'Must be at least 8 characters')
.required('Required')
})
type Schema = InferType<typeof schema>
const state = reactive({
email: undefined,
password: undefined
})
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}
</script>
<template>
<UForm
:schema="schema"
:state="state"
class="space-y-4"
@submit="onSubmit"
>
<UFormField
label="Email"
name="email"
>
<UInput v-model="state.email" />
</UFormField>
<UFormField
label="Password"
name="password"
>
<UInput
v-model="state.password"
type="password"
/>
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>
<script setup lang="ts">
import Joi from '@hapi/joi'
import type { FormSubmitEvent } from '@nuxt/ui'
const schema = Joi.object({
email: Joi.string().required(),
password: Joi.string()
.min(8)
.required()
})
const state = reactive({
email: undefined,
password: undefined
})
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<typeof state>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}
</script>
<template>
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
<UFormField label="Email" name="email">
<UInput v-model="state.email" />
</UFormField>
<UFormField label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>
<script setup lang="ts">
import { object, string, nonempty, refine } from 'superstruct'
import type { Infer } from 'superstruct'
import type { FormSubmitEvent } from '@nuxt/ui'
const schema = object({
email: nonempty(string()),
password: refine(string(), 'Password', (value) => {
if (value.length >= 8) return true
return 'Must be at least 8 characters'
})
})
const state = reactive({
email: '',
password: ''
})
type Schema = Infer<typeof schema>
async function onSubmit(event: FormSubmitEvent<Schema>) {
console.log(event.data)
}
</script>
<template>
<UForm
:schema="schema"
:state="state"
class="space-y-4"
@submit="onSubmit"
>
<UFormField
label="Email"
name="email"
>
<UInput v-model="state.email" />
</UFormField>
<UFormField
label="Password"
name="password"
>
<UInput
v-model="state.password"
type="password"
/>
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>
错误会根据 name 或 error-pattern prop 直接报告给 FormField 组件。这意味着你的模式中为 email 属性定义的验证规则将应用于 <FormField name="email">。
嵌套验证规则使用点符号处理。例如,像 { user: z.object({ email: z.string() }) } 这样的规则将应用于 <FormField name="user.email">。
自定义验证 (Custom Validation)
使用 validate prop 来应用你自己的验证逻辑。
验证函数必须返回一个错误列表,其中包含以下属性:
message- 要显示的错误消息。name- 要发送错误的FormField的name。
schema prop 一起使用,以处理复杂的用例。<script setup lang="ts">
import type { FormError, FormSubmitEvent } from '@nuxt/ui'
const state = reactive({
email: undefined,
password: undefined
})
const validate = (state: any): FormError[] => {
const errors = []
if (!state.email) errors.push({ name: 'email', message: 'Required' })
if (!state.password) errors.push({ name: 'password', message: 'Required' })
return errors
}
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<typeof state>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}
</script>
<template>
<UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit">
<UFormField label="Email" name="email">
<UInput v-model="state.email" />
</UFormField>
<UFormField label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>
输入事件 (Input Events)
当输入发出 input、change 或 blur 事件时,Form 组件会自动触发验证。
input上的验证发生在你输入时。change上的验证发生在你提交值时。blur上的验证发生在输入失去焦点时。
你可以使用 validate-on prop 控制何时进行验证。
useFormField 可组合项在自己的组件中实现这一点。错误事件 (Error Event)
你可以监听 @error 事件来处理错误。此事件在表单提交时触发,并包含一个 FormError 对象数组,其中包含以下字段:
id- 输入的id。name-FormField的namemessage- 要显示的错误消息。
以下是一个示例,它在表单提交后将焦点设置在第一个有错误的输入元素上:
<script setup lang="ts">
import type { FormError, FormErrorEvent, FormSubmitEvent } from '@nuxt/ui'
const state = reactive({
email: undefined,
password: undefined
})
const validate = (state: any): FormError[] => {
const errors = []
if (!state.email) errors.push({ name: 'email', message: 'Required' })
if (!state.password) errors.push({ name: 'password', message: 'Required' })
return errors
}
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<typeof state>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}
async function onError(event: FormErrorEvent) {
if (event?.errors?.[0]?.id) {
const element = document.getElementById(event.errors[0].id)
element?.focus()
element?.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
}
</script>
<template>
<UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit" @error="onError">
<UFormField label="Email" name="email">
<UInput v-model="state.email" />
</UFormField>
<UFormField label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>
嵌套表单 (Nesting Forms)
嵌套表单组件允许你更有效地管理复杂的数据结构,例如列表或条件字段。
例如,它可以用于根据用户输入动态添加字段:
<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
const schema = z.object({
name: z.string().min(2),
news: z.boolean().default(false)
})
type Schema = z.output<typeof schema>
const nestedSchema = z.object({
email: z.string().email()
})
type NestedSchema = z.output<typeof nestedSchema>
const state = reactive<Partial<Schema & NestedSchema>>({ })
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}
</script>
<template>
<UForm
:state="state"
:schema="schema"
class="gap-4 flex flex-col w-60"
@submit="onSubmit"
>
<UFormField label="Name" name="name">
<UInput v-model="state.name" placeholder="John Lennon" />
</UFormField>
<div>
<UCheckbox v-model="state.news" name="news" label="Register to our newsletter" @update:model-value="state.email = undefined" />
</div>
<UForm v-if="state.news" :state="state" :schema="nestedSchema" attach>
<UFormField label="Email" name="email">
<UInput v-model="state.email" placeholder="john@lennon.com" />
</UFormField>
</UForm>
<div>
<UButton type="submit">
Submit
</UButton>
</div>
</UForm>
</template>
或者验证列表输入:
<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
const schema = z.object({
customer: z.string().min(2)
})
type Schema = z.output<typeof schema>
const itemSchema = z.object({
description: z.string().min(1),
price: z.number().min(0)
})
type ItemSchema = z.output<typeof itemSchema>
const state = reactive<Partial<Schema & { items: Partial<ItemSchema>[] }>>({
items: [{}]
})
function addItem() {
if (!state.items) {
state.items = []
}
state.items.push({})
}
function removeItem() {
if (state.items) {
state.items.pop()
}
}
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}
</script>
<template>
<UForm
:state="state"
:schema="schema"
class="gap-4 flex flex-col w-60"
@submit="onSubmit"
>
<UFormField label="Customer" name="customer">
<UInput v-model="state.customer" placeholder="Wonka Industries" />
</UFormField>
<UForm
v-for="item, count in state.items"
:key="count"
:state="item"
:schema="itemSchema"
attach
class="flex gap-2"
>
<UFormField :label="!count ? 'Description' : undefined" name="description">
<UInput v-model="item.description" />
</UFormField>
<UFormField :label="!count ? 'Price' : undefined" name="price" class="w-20">
<UInput v-model="item.price" type="number" />
</UFormField>
</UForm>
<div class="flex gap-2">
<UButton color="neutral" variant="subtle" size="sm" @click="addItem()">
Add Item
</UButton>
<UButton color="neutral" variant="ghost" size="sm" @click="removeItem()">
Remove Item
</UButton>
</div>
<div>
<UButton type="submit">
Submit
</UButton>
</div>
</UForm>
</template>
API
Props
| Prop | Default | Type |
|---|---|---|
state |
An object representing the current state of the form. | |
id |
| |
schema |
Schema to validate the form state. Supports Standard Schema objects, Yup, Joi, and Superstructs. | |
validate |
Custom validation function to validate the form state. | |
validateOn |
|
The list of input events that trigger the form validation. |
disabled |
Disable all inputs inside the form. | |
validateOnInputDelay |
|
Delay in milliseconds before validating the form on input events. |
transform |
|
If true, schema transformations will be applied to the state on submit. |
attach |
|
If true, this form will attach to its parent Form (if any) and validate at the same time. |
loadingAuto |
|
When |
Slots
| Slot | Type |
|---|---|
default |
|
Emits
| Event | Type |
|---|---|
error |
|
submit |
|
Expose
你可以使用 useTemplateRef 访问类型化的组件实例。
<script setup lang="ts">
const form = useTemplateRef('form')
</script>
<template>
<UForm ref="form" />
</template>
这将允许你访问以下内容:
| Name | Type |
|---|---|
submit() | Promise<void> Triggers form submission. |
validate(opts: { name?: keyof T | (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean }) | Promise<T> Triggers form validation. Will raise any errors unless |
clear(path?: keyof T) | void Clears form errors associated with a specific path. If no path is provided, clears all form errors. |
getErrors(path?: keyof T) | FormError[] Retrieves form errors associated with a specific path. If no path is provided, returns all form errors. |
setErrors(errors: FormError[], name?: keyof T) | void Sets form errors for a given path. If no path is provided, overrides all errors. |
errors | Ref<FormError[]> A reference to the array containing validation errors. Use this to access or manipulate the error information. |
disabled | Ref<boolean> |
dirty | Ref<boolean> true if at least one form field has been updated by the user. |
dirtyFields | DeepReadonly<Set<keyof T>> Tracks fields that have been modified by the user. |
touchedFields | DeepReadonly<Set<keyof T>> Tracks fields that the user interacted with. |
blurredFields | DeepReadonly<Set<keyof T>> Tracks fields blurred by the user. |
在 UForm 之外,使用 UButton 提交表单
在 Nuxt UI Pro 中(基于 UForm),你可以在 UForm 组件外部使用按钮提交表单,只需要通过 ref 引用表单实例,然后调用其 submit() 方法即可。
- 给
<UForm>添加ref
<UForm ref="formRef" :state="form" @submit="handleSubmit">
<!-- 表单字段 -->
</UForm>
- 在
<script setup>中定义
const formRef = ref()
const form = reactive({
name: '',
email: ''
})
function handleSubmit(state: typeof form) {
console.log('提交成功:', state)
}
- 表单外部按钮调用
.submit()
<UButton @click="formRef?.submit()">提交表单</UButton>
Theme
export default defineAppConfig({
ui: {
form: {
base: ''
}
}
})
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
form: {
base: ''
}
}
})
]
})
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import uiPro from '@nuxt/ui-pro/vite'
export default defineConfig({
plugins: [
vue(),
uiPro({
ui: {
form: {
base: ''
}
}
})
]
})