FormKit 内置了许多输入类型,但您也可以定义自己的输入,这些输入会自动继承 FormKit 的增值功能,如验证、错误信息、数据建模、分组、标签、帮助文本等。
如果您的用例需要修改现有输入,例如移动部分、更改或重构 HTML 元素等,请考虑使用 FormKit 的输入导出功能。
输入由两个基本部分组成:
如果您刚开始接触自定义输入,请考虑阅读“创建自定义输入”指南。本页内容旨在解释自定义输入的复杂性,适用于编写插件或库等高级用例,并非许多常见用例所必需。
新的输入需要一个输入定义。输入定义可以通过三种方式在 FormKit 中注册:
FormKit
组件上使用 type
属性进行本地注册。输入定义是包含初始化输入所需信息的对象——比如接受哪些属性、渲染哪个架构或组件,以及是否包含任何额外的功能函数。定义对象的结构如下:
{
// 节点类型:input、group 或 list。
type: 'input',
// 要渲染的架构(架构对象或返回对象的函数)
schema: [],
// 要渲染的 Vue 组件(使用 schema _或_ 组件,但不要同时使用)
component: YourComponent,
// (可选)<FormKit> 组件应接受的特定于输入的属性。
// 应为驼峰式字符串数组
props: ['fooBar'],
// (可选)接收节点的函数数组。
features: []
}
type
属性让我们创建一个最简单的输入 —— 一个只输出 "Hello world" 的输入。
尽管这个简化的例子没有包含任何输入/输出机制,它仍然符合完整输入的标准。它可以有一个值,运行验证规则(它们不会显示,但它们可以阻止表单提交),并执行插件。从根本上说,所有输入都是核心节点,输入的定义提供了与该节点交互的机制。
要在应用程序中通过 "type" 字符串(例如:<FormKit type="foobar" />
)使用您的自定义输入,您可以向 defaultConfig
选项中添加一个 inputs
属性。inputs
对象的属性名称将成为应用程序中 <FormKit>
组件可用的 "type" 字符串。
import { createApp } from 'vue'
import App from 'App.vue'
import { plugin, defaultConfig } from '@formkit/vue'
const helloWorld = {
type: 'input',
schema: ['Hello world'],
}
createApp(App)
.use(
plugin,
defaultConfig({
inputs: {
// 属性将是 <FormKit type="hello"> 中的 “type”
hello: helloWorld,
},
})
)
.mount('#app')
现在我们已经定义了我们的输入,我们可以在应用程序中任何地方使用它:
上面的例子扩展了 @formkit/inputs
库(通过 defaultConfig
)。然而,FormKit 的一个强大特性是它能够从多个插件加载输入库。然后,这些输入可以在任何可以定义插件的地方注册:
让我们将我们的 hello world 输入重构为使用它自己的插件:
请注意,在上面的例子中,我们的插件是在实际使用它的元素的父级上定义的!这要归功于插件继承 —— FormKit 插件的核心特性。
您的输入可以使用 FormKit 的 schema 或一个通用的 Vue 组件来编写。每种方法都有其优缺点:
代码 | 优点 | 缺点 |
---|---|---|
Vue |
|
|
Schema |
|
|
即使您更喜欢使用标准 Vue 组件来编写自定义输入,您仍然可以在输入定义中使用 schema。请阅读使用 createInput
来扩展基础 schema 部分。
主要的结论是,如果您计划在多个项目中使用自定义输入 —— 那么考虑使用基于 schema 的方法。如果您的自定义输入只会在单个项目中使用,并且灵活性不是一个问题,使用 Vue 组件。
将来,FormKit 可能会扩展以支持其他框架(例如:React 或 Svelte。如果您对此感兴趣,请告诉我们!)。使用 schema 编写您的输入意味着您的输入也将兼容这些框架(可能需要最小的更改)。
FormKit 的所有核心输入都是使用 schema 编写的,以提供尽可能最大的灵活性。编写您自己的 schema 输入时,您有两个主要选项:
了解“标准”FormKit 输入的基本结构是很重要的,它被分解成部分:
上图中的 input
部分通常是您在创建自己的输入时会替换的部分 —— 保持包装器、标签、帮助文本和消息不变。然而,如果您也想控制这些方面,您也可以从头开始编写自己的输入。
createInput
扩展基础 schema要使用基础 schema 创建输入,您可以使用 @formkit/vue
包中的 createInput()
工具。这个函数接受 3 个参数:
input
部分(见上图)。该函数返回一个可立即使用的输入定义。
当提供一个 组件 作为第一个参数时,createInput
将生成一个在基础 schema 中引用您的组件的 schema 对象。您的组件将传递一个单一的 context
属性:
{
$cmp: 'YourComponent',
props: {
context: '$node.context'
}
}
当提供一个 schema 对象时,您的 schema 将直接注入到基础 schema 对象中。注意,我们的 hello world 示例现在支持输出“标准”的 FormKit 功能,如标签、帮助文本和验证:
有时候,完全从零开始编写输入而不使用任何基础 schema 功能是有意义的。这样做时,只需提供您的完整 schema 对象的输入定义。
在上面的例子中,我们能够重新创建 createInput
示例的相同功能 —— 即 —— 标签、帮助文本和验证消息输出。然而,我们仍然缺少一些“标准”的 FormKit 功能,如插槽支持。如果您试图发布您的输入或保持与其他 FormKit 输入的 API 兼容性,请查看输入清单。
在使用 Vue 组件编写自定义 FormKit 输入时,建议不要在内部使用 FormKit 组件,自定义输入应该像常规输入一样编写,但可以利用 FormKit 上下文属性来添加 FormKit 所需的功能。如果您的情况是要使用带有默认值的 FormKit 组件,建议改用 Vue 组件包装器并直接调用该组件,FormKit 输入可以在任何嵌套级别上工作,或者您也可以考虑使用 FormKit 的输入导出功能来添加功能以及更改属性和属性。
对于大多数用户来说,将 Vue 组件传递给 createInput
在定制化和增值功能之间提供了一个良好的平衡。如果您想要完全脱离基于 schema 的输入,您可以直接将一个组件传递给输入定义。
组件输入接收单个属性 —— 上下文对象。然后由您编写一个组件来包含 FormKit 的期望功能(标签、帮助文本、消息显示等)。查看输入清单以获取您将要输出的内容列表。
输入有两个关键角色:
您可以通过任何用户交互接收输入,并且输入可以设置为任何类型的数据。输入不仅限于字符串和数字 —— 它们可以愉快地存储数组、对象或自定义数据结构。
从根本上说,输入所需要做的就是用一个值调用 node.input(value)
。node.input()
方法是自动防抖的,所以请随意频繁调用 —— 比如每次敲击键盘。通常,这看起来像是绑定到 input
事件。
上下文
对象 包括一个基本输入类型的输入处理器:context.handlers.DOMInput
。这可以用于文本类输入,其中输入的值可在 event.target.value
处获得。如果您需要更复杂的事件处理器,您可以使用“特性”来暴露它。
任何用户交互都可以被视为输入事件。对于许多原生 HTML 输入,该交互是通过 input 事件 捕获的。
// 用 schema 编写的 HTML 文本输入:
{
$el: 'input',
attrs: {
onInput: '$handlers.DOMInput'
}
}
在 Vue 模板中的等效写法:
<template>
<input @input="context.DOMInput" />
</template>
输入组件同样负责显示当前值。通常,你会想使用 node._value
或 $_value
在 schema 中来显示一个值。这是“实时”的非防抖值。当前的 已提交 值是 node.value
($value
)。阅读更多关于“值的确定”这里。
// 一个在 schema 中编写的 HTML 文本输入:
{
$el: 'input',
attrs: {
onInput: '$handlers.DOMInput',
value: '$_value'
}
}
在 Vue 模板中的等效写法:
<template>
<input :value="context._value" @input="context.handlers.DOMInput" />
</template>
未提交输入 _value
应该使用的唯一时间是在输入本身上显示值 — 在所有其他位置,使用已提交的 value
是很重要的。
你可以将 标准 FormKit 属性(如 label
或 type
)传递给 <FormKit>
组件,这些属性在 context 对象的根部和 core node props
中可用,你可以在你的 schema 中直接引用这些属性表达式(例如:$label
)。传递给 <FormKit>
组件的任何非 节点属性 都会出现在 context.attrs
对象中(在 schema 中就是 $attrs
)。
如果你需要额外的属性,你可以在输入定义中声明它们。属性也可以用来接受来自 <FormKit>
组件的新属性,但它们也用于内部输入状态(很像 Vue 3 组件中的 ref
)。
FormKit 使用 props
命名空间来处理这两种用途(请参阅下面的自动完成示例)。属性应 始终 以 camelCase 定义,并在你的 Vue 模板中以 kebab-case 使用。有 2 种定义属性的方法:
使用 createInput
帮助器扩展基本 schema 时,传递一个带有输入定义值的第二个参数进行合并:
对象表示法通过让你能够精细控制如何定义你的属性,给了你这样的能力:
boolean
属性。node.addProps()
)您可以在任何可以访问节点的运行时环境中使用 node.addProps()
方法动态添加属性。对于自定义输入,这在特性中使用时特别有帮助。支持数组表示法和对象表示法(见上文)。
特性是向自定义输入类型添加功能的首选方式。一个“特性”仅仅是一个函数,它接收 核心节点 作为参数。实际上,它们是没有继承的插件(所以它们只适用于当前节点)。您可以使用特性来添加输入处理器、操作值、与属性交互、监听事件等等。
特性在数组中定义,以鼓励在可能的情况下重用代码。例如,我们在 select
、checkbox
和 radio
输入上使用了一个名为“options”的特性。
作为一个例子,假设您想构建一个输入,允许用户输入两个数字,输入的值是这两个数字的和:
FormKit 是用 TypeScript 编写的,并包含了其所有核心输入的类型定义。如果您正在编写自己的输入并希望提供 TypeScript 支持,您可以使用两个模块增强来定义自己的输入:
<FormKit>
组件的 type
属性是一个字符串,用作属性的鉴别联合(FormKitInputProps
)的键。通过增强这个类型,您的自定义输入可以定义它们自己的属性类型。要做到这一点,您必须增强 FormKitInputProps
类型以添加您自己的自定义类型:
declare module '@formkit/inputs' {
interface FormKitInputProps<Props extends FormKitInputs<Props>> {
// 这个键和 `type` 应该匹配:
'my-input': {
// 定义您的输入 `type`:
type: 'my-input',
// 定义一个可选属性。所有属性名称使用驼峰式命名:
myOptionalProp?: string | number
// 定义一个必需的属性
superImportantProp: number
// 定义值类型,这应该始终是可选的!
value?: string | number
// 使用 Prop 泛型从另一个字段推断信息,注意
// 我们使用一个实用工具 "PropType" 来从 Props
// 泛型推断 `value` 的类型:
someOtherProp?: PropType<Props, 'value'>
}
}
}
如果您在自定义输入中定义了自己的部分(插槽),您也可以为这些部分添加TypeScript支持。要做到这一点,您必须扩展FormKitInputSlots
类型以添加您自己的自定义插槽:
declare module '@formkit/inputs' {
interface FormKitInputProps<Props extends FormKitInputs<Props>> {
'my-input' {
type: 'my-input'
// ... 在这里添加属性
}
}
interface FormKitInputSlots<Props extends FormKitInputs<Props>> {
'my-input': FormKitBaseSlots<Props>
}
}
在上面的例子中,我们使用了FormKitBaseSlots
— 一个TypeScript工具,用于添加大多数自定义输入实现的“基本”插槽,如outer
、label
、help
、message
等。然而,您也可以完全从头开始定义自己的插槽,或者扩展FormKitBaseSlots
以添加额外的插槽(FormKitBaseSlots<Props> & YourCustomSlots
)。
declare module '@formkit/inputs' {
// ... 在这里添加属性
interface FormKitInputSlots<Props extends FormKitInputs<Props>> {
'my-input': {
// 这将是my-input输入上*唯一*可用的插槽
slotName: FormKitFrameworkContext & {
// 这将作为`slotName`插槽中的插槽数据可用
fooBar: string
}
}
}
}
}
为了扩展FormKitInputSlots
,您必须首先编写一个FormKitInputProps
的扩展,至少包括type
属性。
以下是一些自定义输入的示例。它们并不意味着全面或适合生产环境,而是用来说明一些自定义输入功能。
这是最简单的可能输入,它不利用FormKit内置的任何DOM结构,只输出一个文本输入 — 然而它是其嵌套组内的一个完全功能的成员,并能够读取和写入值。
在上面的示例中,$handlers.DOMInput
是一个内置的便利函数,用于 (event) => node.input(event.target.value)
。
让我们来看一个稍微复杂一些的示例,它利用createInput
提供所有标准的FormKit结构,同时仍然提供自定义输入界面。
FormKit 为即使是最普通的输入暴露了数十个增值特性。当为特定项目编写自定义输入时,你只需要实现实际将在该项目上使用的特性。然而,如果你计划将你的输入分发给其他人,你会希望确保这些特性是可用的。例如,标准的 <FormKit type="text">
输入使用以下模式为其 input
元素:
{
$el: 'input',
bind: '$attrs',
attrs: {
type: '$type',
disabled: '$disabled',
class: '$classes.input',
name: '$node.name',
onInput: '$handlers.DOMInput',
onBlur: '$handlers.blur',
value: '$_value',
id: '$id',
}
}
在上面的模式中有几个特性可能不是立即显而易见的,比如 onBlur
处理器。以下清单旨在帮助输入作者覆盖所有基础:
label
prop must be displayed and linked for accessibility with the for
attribute.label
slot.label
section key.help
prop must be displayed.help
slot.help
section key.context.messages
object must displayed if it is set to visible
.messages
slot.messages
section key.message
slot.message
section key..input
slot.id
attribute (context.id
).name
attribute (context.node.name
).context.handlers.blur
when blurred.node.input(value)
when the user provides input. You can use context.handlers.DOMInput
for text-like inputs.context._value
.disabled
attribute when context.disabled
is true
.bind: '$attrs'
in schemas.