自定义输入

构建您的第一个自定义输入?阅读指南

FormKit包含了许多输入,但您也可以定义自己的输入,自动继承FormKit的增值功能,如验证、错误消息、数据建模、分组、标签、帮助文本等。

修改或重组现有输入

如果您的用例需要修改现有输入,比如移动部分、更改或重组HTML元素等,请考虑使用FormKit的输入导出功能

输入由两个基本部分组成:

  1. 输入定义
  2. 输入的代码:模式组件
从指南开始

如果您刚开始使用自定义输入,请阅读“创建自定义输入”指南。本页面的内容旨在解释自定义输入的复杂性,适用于高级用例,如编写插件或库,并不适用于许多常见用例。

注册输入

新的输入需要一个输入定义。输入定义可以通过以下三种方式注册到FormKit:

输入定义

输入定义是包含初始化输入所需信息的对象,例如要接受的props、要渲染的schema或组件以及是否包含任何其他功能函数。定义对象的形状如下:

{
  // 节点类型:input、group或list。
  type: 'input',
  // 要渲染的模式(模式对象或返回对象的函数)
  schema: [],
  // 要渲染的Vue组件(使用模式 _OR_ 组件,但不能同时使用)
  component: YourComponent,
  // (可选)<FormKit>组件应接受的特定于输入的props。
  // 应为驼峰式字符串的数组
  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')

现在,我们已经定义了我们的输入,可以在应用程序的任何地方使用它:

加载实时示例

插件库

上面的示例通过defaultConfig扩展了@formkit/inputs库。然而,FormKit的一个强大功能是它能够从多个插件中加载输入库。这些输入可以在任何可以定义插件的地方进行注册:

  • 全局
  • 每个分组
  • 每个表单
  • 每个列表
  • 每个输入

让我们重构我们的hello world输入,使用自己的插件:

加载实时示例
插件继承

请注意,在上面的示例中,我们的插件是在实际使用它的元素的父级上定义的!这要归功于FormKit插件的插件继承 - 这是FormKit插件的一个核心功能。

模式 vs 组件

您的输入可以使用 FormKit 的模式 或通用的 Vue 组件来编写。每种方法都有其优缺点:

代码优点缺点
Vue
  • 学习曲线(您可能已经知道如何编写 Vue 组件)。
  • 更成熟的开发工具。
  • 初始渲染速度稍快。
  • 无法使用 :sections-schema 属性 来修改结构。
  • 插件无法修改模式以更改渲染输出。
  • 特定于框架(仅限 Vue)。
  • 很容易编写与 FormKit 生态系统不兼容的输入。
模式
  • 可以通过 :sections-schema 属性修改结构(如果允许)。
  • 插件可以修改/更改渲染输出。
  • 框架无关(将来支持新框架时可移植性更强)。
  • 生态系统兼容性(非常适合发布自己的开源输入)。
  • 学习曲线(需要 了解模式)。
  • 初始渲染速度稍慢。
  • 开发工具不太成熟。
模式中的组件

即使您更喜欢使用标准的 Vue 组件编写自定义输入,您仍然可以在输入定义中使用模式。请阅读使用 createInput 扩展基本模式部分。

主要的要点是,如果您计划在多个项目中使用自定义输入,则考虑使用基于模式的方法。如果您的自定义输入仅在单个项目中使用且灵活性不是问题,则使用 Vue 组件。

未来的兼容性

将来,FormKit 可能会扩展以支持其他框架(例如 React 或 Svelte)。如果您对此感兴趣,请告诉我们!。使用模式编写输入意味着您的输入也将与这些框架兼容(可能需要进行最小更改)。

模式输入

FormKit 的所有核心输入都是使用模式编写的,以实现最大的灵活性。在编写自己的模式输入时,您有两个主要选项:

重要的是要了解“标准” FormKit 输入的基本结构,它被分解为部分

电子邮件地址
🤟
test@example.com
🚀
请使用您的学校电子邮件地址。
请提供有效的电子邮件地址。
标准 FormKit 文本输入的组成部分。

上图中的 input 部分通常是您在创建自己的输入时要替换的部分,但保持包装器、标签、帮助文本和消息不变。但是,如果您还想控制这些方面,您也可以从头开始编写自己的输入。

使用 createInput 扩展基本模式

要使用基本模式创建输入,您可以使用 @formkit/vue 包中的 createInput() 实用程序。此函数接受 2 个参数:

  • (必需)一个模式节点 一个 Vue 组件,它将插入到基本模式的 input 部分(参见上图)。
  • (可选)一个要与自动生成的输入定义属性合并的输入定义对象。

该函数返回一个可直接使用的输入定义

当将 组件 作为第一个参数提供时,createInput 将生成一个引用您组件的模式对象,并将一个 context 属性传递给您的组件:

{
  $cmp: 'YourComponent',
  props: {
    context: '$node.context'
  }
}

当提供模式对象时,您的模式将直接注入到基本模式对象中。请注意,我们的示例现在支持输出“标准” FormKit 功能,如标签、帮助文本和验证:

加载实时示例

从头开始编写模式输入

完全从头开始编写输入,而不使用任何基本模式功能可能是有意义的。在这种情况下,只需提供input定义您的完整模式对象。

加载实时示例

在上面的示例中,我们能够重新创建与createInput示例相同的功能,即标签、帮助文本和验证消息输出。然而,我们仍然缺少许多“标准”的FormKit功能,如插槽支持。如果您尝试发布您的输入或保持与其他FormKit输入的API兼容性,请查看input清单

组件输入

对于大多数用户来说,将Vue组件传递给createInput提供了定制和增值功能之间的良好平衡。如果您完全想要退出基于模式的输入,您可以直接将组件传递给输入定义。

组件输入接收一个单一的属性 - 上下文对象。然后,您可以编写一个组件来包含FormKit所需的功能(标签、帮助文本、消息显示等)。请查看input清单以获取您想要输出的内容列表。

输入和输出值

输入有两个关键角色:

  • 接收用户输入。
  • 显示当前值。

接收输入

您可以从任何用户交互中接收输入,并且输入可以将其值设置为任何类型的数据。输入不仅限于字符串和数字 - 它们可以愉快地存储数组、对象或自定义数据结构。

从根本上说,输入只需要使用一个值调用node.input(value)node.input()方法会自动进行防抖处理,因此可以随意频繁调用它 - 比如每次按键。通常,这看起来像是绑定到input事件。

context对象包括用于基本输入类型的输入处理程序:context.handlers.DOMInput。这可以用于文本类输入,其中输入的值在event.target.value中可用。如果您需要更复杂的事件处理程序,可以使用“功能”来公开它。

任何用户交互都可以视为输入事件。对于许多原生HTML输入,该交互是通过input事件捕获的。

// 在模式中编写的HTML文本输入:
{
  $el: 'input',
  attrs: {
    onInput: '$handlers.DOMInput'
  }
}

在Vue模板中的等效写法:

<template>
  <input @input="context.DOMInput" />
</template>

显示值

输入还负责显示当前值。通常,您会希望在模式中使用node._value$_value来显示值。这是“实时”的非防抖值。当前已提交的值是node.value$value)。在这里阅读更多关于“值结算”的内容。

// 在模式中编写的HTML文本输入:
{
  $el: 'input',
  attrs: {
    onInput: '$handlers.DOMInput',
    value: '$_value'
  }
}

在Vue模板中的等效写法:

<template>
  <input :value="context._value" @input="context.handlers.DOMInput" />
</template>
_value vs value

只有在输入本身上显示值时才应使用未提交的输入_value - 在所有其他位置,重要的是使用已提交的value

添加属性

您可以在模式中直接引用表达式(例如:$label)来使用根据<FormKit>组件传递的标准FormKit属性(如labeltype)。任何传递给<FormKit>组件的不是节点属性的属性最终都会出现在context.attrs对象中(在模式中只是$attrs)。

如果您需要其他属性,可以在输入定义中声明它们。属性也可以用于内部输入状态(类似于Vue 3组件中的ref)。FormKit将props命名空间用于这两个目的(有关此示例的自动完成示例,请参见下文)。属性应始终以驼峰命名法定义,并在Vue模板中使用短横线命名法。

加载实时示例

通过使用createInput助手来扩展基本模式时,传递第二个参数与输入定义值进行合并:

加载实时示例

添加功能

功能是向自定义输入类型添加功能的首选方法。 "功能"只是一个接收核心节点作为参数的函数。实际上,它们是没有继承的插件(因此它们只适用于当前节点)。您可以使用功能来添加输入处理程序,操作值,与属性交互,监听事件等等。

功能在数组中定义,以鼓励尽可能地重用代码。例如,我们在selectcheckboxradio输入上使用了一个名为“options”的功能。

例如,假设您想构建一个输入,允许用户输入两个数字,而输入的值是这两个数字的和:

加载实时示例

示例

以下是一些自定义输入的示例。它们不打算全面或用于生产环境,而是用于说明一些自定义输入功能。

简单文本输入

这是最简单的输入,不利用FormKit的内置DOM结构,只输出一个文本输入 - 但它是所嵌套组中的一个完全功能的成员,能够读取和写入值。

加载实时示例
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处理程序。以下清单旨在帮助输入作者涵盖所有方面:

How is your input built?
  • The outermost wrapper element on all FormKit inputs.
  • The value of the label prop must be displayed and linked for accessibility with the for attribute.
  • Users can override the label slot.
  • Users can extend the label section using the label section key.
  • The value of the help prop must be displayed.
  • Users can override the help slot.
  • Users can extend the help section using the help section key.
  • Each message in the context.messages object must displayed if it is set to visible.
  • Users can override the messages slot.
  • Users can extend the messages section using the messages section key.
  • Users can override the message slot.
  • Users can extend the message section using the message section key..
  • Users can override the input slot.
  • The primary input element should include an id attribute (context.id).
  • The primary input element should include a name attribute (context.node.name).
  • The primary input element should call context.handlers.blur when blurred.
  • The primary input element should call node.input(value) when the user provides input. You can use context.handlers.DOMInput for text-like inputs.
  • The primary input element should display the current value of the input using context._value.
  • The primary input element should apply the disabled attribute when context.disabled is true.
  • All events bindings should be passed through. Use bind: '$attrs' in schemas.
  • Classes for all DOM elements should be applied using context.classes.{section-key}.