npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

slimeform

v0.10.0

Published

Form state management and validation for Vue3

Downloads

943

Readme


表单状态管理和数值校验

由来

在 Vue 项目中,我们经常会使用各种预制的表单组件,它可能是自己编写的,或者来自第三方 UI 库。对于第三方 UI 库而言,它们可能有自己的表单校验器,而自己的组件则需要自己编写表单校验器,很多时候这些表单的校验器是不统一的,尤其是当你在项目中混合使用第三方 UI 库 和自己的组件时,将它们结合在一起会十分困难。

基于现代 CSS 工具类和组件化,编写自己风格的 <input> 组件并将它们组合成表单十分容易,然而在涉及到需要整合所有输入相关的表单状态管理和规则校验时,问题就变得复杂起来了。

因此我开始为满足这一需求而对一个解决方案进行实验,并将其取名为 SlimeForm,含义为像史莱姆一样嵌入并粘粘所有表单 💙。

SlimeForm 是一个无组件无内置规则的表单状态管理器和验证器,通过 v-model 绑定所有的原生或自定义组件,并响应式地管理以及验证它们。

待办事项

  • [x] 改进功能
    • [x] 使用 reactive 类型返回表单
    • [x] 对于单个规则,可以省略数组
    • [x] 使用 status[key].isDirty 标记表单的值是否被修改
  • [x] 文档
  • [x] 更好的 Typescript 类型定义
  • [x] 单元测试
  • [x] 添加对 object 类型字段的支持
  • [ ] 添加对异步规则的支持
  • [x] 支持过滤未修改的条目,只留下已经修改的条目进行提交
  • [ ] 支持第三方规则,比如 yup
    • [x] 支持 validateSync
    • [ ] 支持 validate(异步)
  • [ ] 💡 更多的点子

欢迎贡献

在线尝试

🚀 slimeform-playground

安装

⚗️ 实验性

npm i slimeform

SlimeForm 仅支持 Vue 3

使用方式

表单状态管理

formv-model 绑定到 <input> 或是其他组件。 值改变时 status 会产生对应的变化;使用 reset 方法重置表单的值到初始状态。

<script setup>
import { useForm } from 'slimeform'

const { form, status, reset, dirtyFields } = useForm({
  // 初始的 form 值
  form: () => ({
    username: '',
    password: '',
  }),
})
</script>

<template>
  <form @submit.prevent="mySubmit">
    <label>
      <!-- 这里 -->
      <input v-model="form.username" type="text">
      <input v-model="form.password" type="text">
    </label>
    <button type="submit">
      提交
    </button>
  </form>
</template>

状态管理

const { form, status, reset, isDirty } = useForm(/* ... */)

// 表单是否已经被修改
isDirty.value
// username 是否已经被修改
status.username.isDirty
// password 是否已经被修改
status.password.isDirty

// 重置表单, 恢复到初始状态
reset()

// 重置指定字段
reset('username', 'password', /* ... */)

可变的初始 form 值

useForm 的表单初始值可以是其它变量或 pinia 的状态,初始值的改变将在表单重置时同步到 form 对象中

const userStore = useUserStore()

const { form, reset } = useForm({
  form: () => ({
    username: userStore.username,
    intro: userStore.intro,
  }),
})

// 假设我们调用 setInfo 函数更新了 username 和 intro 的值
userStore.setInfo(/* ... */)
// 在调用 reset 时,对 `userStore` 所做的更改将同步到 `form` 对象中
reset()

// 这些属性将是之前调用过 `setInfo` 之后 `userStore` 的值
form.username
form.intro

筛选出已修改的字段

假设你正在开发一个对现有数据进行编辑的表单,用户通常只会修改其中的部分字段,然后前端会通过 HTTP PATCH 将用户修改过的那部分字段提交给后端,后端依据提交了哪些字段进行局部更新

此类需求可以使用 dirtyFields 计算函数,它的值是一个只会包含 form 当中被修改过字段的对象。

const { form: userInfo, status, dirtyFields } = useForm(/* ... */)

dirtyFields.value /* value: {} */

// 编辑用户简介
userInfo.intro = 'abcd'

dirtyFields.value /* value: { intro: 'abcd' } */

// 编辑用户简介为默认值
userInfo.intro = '' /* default value */

dirtyFields.value /* value: {} */

表单规则校验

使用 rule 定义表单字段的验证规则。当字段值发生更改时,验证过程将自动进行,验证结果将会存储和呈现在 status[key].isErrorstatus[key].message 属性中。 如果一个字段需要多个规则,可以使用函数数组来声明。

你可以维护自己的规则集,并在需要使用的地方导入。

// formRules.ts
export function isRequired(value) {
  if (value && value.trim())
    return true

  return t('required') // i18n 支持
}
<script setup>
import { isRequired } from '~/util/formRules.ts'
const {
  form,
  status,
  submitter,
  clearErrors,
  isError,
  verify
} = useForm({
  // 初始 form 值
  form: () => ({
    name: '',
    age: '',
  }),
  // 进行校验
  rule: {
    name: isRequired,
    // 如果一个字段有多条规则,可以使用数组
    age: [
      isRequired,
      // 要求字段是数字 number
      val => !Number.isNaN(val) || 'Expected number',
      // 要求字段有最大值
      val => val.length < 3 || 'Length needs to be less than 3',
    ],
  },
})

const { submit } = submitter(() => {
  alert(`Age: ${form.age} \n Name: ${form.name}`)
})
</script>

<template>
  <form @submit.prevent="submit">
    <!-- ... -->
  </form>
</template>

此外,您可以在验证错误消息中使用任何响应式的值,例如如上所示,对 vue-i18n 库的多语言函数 t('required') 的调用。

const { _, status, verify } = useForm(/* ... */)
// 表单校验
verify()
// 字段校验
status.username.verify()
status.username.setError('username has been registered')
const { _, status, clearErrors, reset } = useForm(/* ... */)
// 清除字段的错误
status.username.clearError()
// 清除全部错误
clearErrors()
// 重置表单也会清除错误
reset()

isError: 是否有任何表单字段包含错误的验证结果

const { _, isError } = useForm(/* ... */)

isError /* true / false */

使用 defaultMessage 定义表单字段校验信息的占位内容。默认值为 '',你可以将它设置为 \u00A0,在渲染时会被转义为 &nbsp;,以此来避免没有 message 时 <p> 出现高度坍塌问题。

const { form, status } = useForm({
  form: () => ({/* ... */}),
  rule: {/* ... */},
  // 没有错误消息时的占位内容
  defaultMessage: '\u00A0',
})

lazy 设置为 true 可以阻止数据变化时规则自动校验。

此时, 可以考虑调用 verify()status[fieldName].verify() 来手动校验字段。

const { form, status, verify } = useForm({
  form: () => ({
    userName: '',
    /* ... */
  }),

  rule: {
    userName: v => v.length < 3,
  },

  lazy: true,
})

form.userName = 'abc'
status.userName.isError // false

verify()

status.userName.isError // true

Slimeform 在 useForm() 的返回值中提供了 rule, 该字段可以校验表单外的数据。如果您想确保数据在传入表单之前一定是满足规则的,那么这个字段是很有用的。

const { form, rule } = useForm({
  form: () => ({
    userName: '',
    /* ... */
  }),

  rule: {
    userName: v => v.length < 3 || 'to many characters',
  },
})

const text = 'abcd'
const isValid = rule.userName.validate(text) // false
if (isValid)
  form.userName = text

您还可以获取到该字段的错误信息,方法是在第二个选项参数里传入 fullResult: true。此时该方法会返回一个带有错误信息的对象。

rule.userName.validate('abcd', { fullResult: true }) // { valid: false, message: "to many characters" }
rule.userName.validate('abc', { fullResult: true }) // { valid: true, message: null }

提交

submitter 接受一个回调函数参数,返回触发这个回调函数的函数和表示函数运行中的状态变量;传入 submitter 的回调函数可以拿到 useForm 函数返回的所有状态和函数,这样可以将回调函数放到单独的代码中,甚至编写通用的提交函数,方便组合使用。

<script setup>
import { useForm } from 'slimeform'

const { _, submitter } = useForm(/* ... */)

// Define the submit function
const {
  // trigger submit callback
  submit,
  // Indicates whether the asynchronous commit function is executing
  submitting,
} = submitter(async ({ form, status, isError, reset /* ... */ }) => {
  // Submission Code
  const res = await fetch(/* ... */)
  // ....
})
</script>

<template>
  <form @submit.prevent="submit">
    <!-- ... -->

    <!-- Use `submitting` to disable buttons and add loading indicator -->
    <button type="submit" :disabled="submitting">
      <icon v-if="submitting" name="loading" />
      Submit
    </button>
  </form>
</template>

默认情况下调用 submit 函数后会先进行表单规则验证,如果验证失败,将会直接结束函数执行,如果需要关闭此行为可以在 submitter 函数的第二个参数参数 options 中配置 enableVerify: false 来跳过验证。

包装通用提交函数并使用

import { mySubmitForm } from './myFetch.ts'
const { _, submitter } = useForm(/* ... */)
// Wrap the generic submission code and use it later
const { submit, submitting } = submitter(mySubmitForm({ url: '/register', method: 'POST' }))

集成

使用 Yup 作为规则

如果你不想自己编写验证规则的细节,已经有一种非常简洁的方法可以使用 Yup 作为规则。

SlimeForm 内置了 Yup 同步规则的解析器:yupFieldRule,你可以从 slimeform/resolvers 导入它。yupFieldRule 函数在内部调用 schema.validateSync 方法,并处理结果为 SlimeForm 可接受的格式。

首先,你要安装 Yup

npm install yup

然后在代码中导入 yupyupFieldRule 就可以使用了

import { useForm } from 'slimeform'
import * as yup from 'yup'

/* 导入解析器 */
import { yupFieldRule } from 'slimeform/resolvers'

const { t } = useI18n()

const { form, status } = useForm({
  form: () => ({ age: '' }),
  rule: {
    /* 一些使用案例 */
    age: [
      yupFieldRule(yup.string()
        .required(),
      ),
      yupFieldRule(yup.number()
        .max(120, () => t('xxx_i18n_key'))
        .integer()
        .nullable(),
      ),
    ],
  },
})

建议

一些建议:

  1. 使用 @submit.prevent 而不是 @submit 来屏蔽表单默认提交行为
  2. 使用 isError 的值来动态地判断是否需要给表单输入框添加红色的描边
<template>
  <h3>请输入你的年龄</h3>
  <form @submit.prevent="submitFn">
    <label>
      <input
        v-model="form.age"
        type="text"
        :class="status.age.isError && '!border-red'"
      >
      <p>{{ status.age.message }}</p>
    </label>
    <button type="submit">
      提交
    </button>
  </form>
</template>