表单是前端开发中最棘手的部分之一,您可能会在其中发现不少混乱的代码。javascript
基于组件的框架,如 Vue.js,在提升前端代码的可扩展性方面作了不少工做,可是表单的问题仍然存在。css
在本教程中,将向您展现新的 Vue Composition API(即将加入 Vue 3 中)如何使表单代码更清晰、更具可扩展性。前端
像 Vue 这种基于组件的框架的关键设计模式是组件组合。vue
这种模式将应用程序的特性抽象为独立的、单一用途的组件,这些组件通讯使用 props 和事件的方式。java
然而,在此模式下,不能很好地对表单进行抽象,由于表单的功能和状态显然不属于任何一个组件,所以将其分离一般会致使与解决的问题同样多的问题。git
在 Vue 中表单代码写的烂的另外一个重要缘由是,直到 Vue2 以前, 尚未提供强大的手段在组件之间重用代码。重用代码对表单来讲很重要,由于表单输入一般有明显的不一样,但在功能上有许多类似之处。github
Vue2 提供的代码重用的主要方法是 mixin,我认为这是一个明显的反模式。npm
早在2016年中期,丹·阿布拉莫夫(Dan Abramov)就写了《mixin被认为是有害的》(mixin Considered Harmful),他在书中辩称,将 mixin 用于在 React 组件中重用逻辑是一种反模式,主张远离它们。后端
不幸的是,他提到的关于 React mixins 的缺点一样适用于 Vue。在了解 Composition API 克服这些缺点以前,让咱们熟悉这些缺点。设计模式
使用 mixin 模式在运行时合并两个对象,若是他们两个都共享同名属性,会发生什么?
const mixin = { data: () => ({ myProp: null }) } export default { mixins: [mixin], data: () => ({ // 同名! myProp: null }) }
这就是合并策略发挥做用的地方。这是一组规则,用于肯定当一个组件包含多个具备相同名称的选项时会发生什么。
Vue 组件的默认(可选配置)合并策略指示本地选项将覆盖 mixin 选项。不过也有例外,例如,若是咱们有多个相同类型的生命周期钩子,这些钩子将被添加到一个钩子数组中,而且全部的钩子都将被依次调用。
尽管咱们不该该遇到任何实际的错误,可是在跨多个组件和 mixin 处理命名属性时,编写代码变得愈来愈困难。一旦第三方 mixin 做为带有本身命名属性的 npm 包被添加进来,就会特别困难,由于它们可能会致使冲突。
mixin 和使用它的组件之间没有层次关系。
这意味着组件可使用 mixin 中定义的数据属性(例如mySharedDataProperty),可是 mixin 也可使用组件中定义的数据属性(例如myLocalDataProperty)。这种状况一般是在 mixin 被用于共享输入验证时出现的,mixin 可能会指望一个组件有一个输入值,它将在本身的 validate 方法中使用。
不过,这可能会引发一些问题。若是咱们之后想重构一个组件,改变了 mixin 须要的变量名称,会发生什么状况呢?咱们在看这个组件时,不会发现有什么问题。代码检查也不会发现它,只会在运行时看到错误。
如今想象一个有不少 mixin 的组件。咱们能够重构本地数据属性吗?或者它会破坏 mixin 吗?咱们得手动搜索才能知道。
mixins 的缺点是 Composition API 背后的主要推进因素之一,来看看它如何克服 mixin 的问题,写出清晰、可扩展的表单代码。
经过 Vue CLI 建立一个项目,将 Composition API 做为插件添加到 Vue 2 项目中。
$ vue create composition-api-form $ cd composition-api-form $ npm i -S @vue/composition-api
接下来,在 main.js 中加入这个插件
import Vue from "vue"; import App from "./App.vue"; import VueCompositionApi from "@vue/composition-api"; Vue.use(VueCompositionApi); new Vue({ render: h => h(App) }).$mount('#app');
为了使这个例子简单,咱们将建立一个仅包含输入名字和电子邮件的独立的组件。
$ touch src/components/InputName.vue $ touch src/components/InputEmail.vue
设置 InputName 组件模板,包括一个 HTML 输入元素,并使用 v-model 指令建立双向绑定。
src/components/InputName.vue
<template> <div> <label> Name <input type="text" v-model="input" name="name" /> </label> </div> </template> <script> export default { name: 'InputName' } </script>
将添加 novalidate 属性,让浏览器知道咱们将提供自定义验证。还将监听表单的 submit 事件,防止表单自动提交,并使用声明的 onSubmit 方法处理该事件。
而后,添加 InputName 和 InputEmail 组件,并分别将本地状态值 name 和 email 进行绑定。
src/App.vue
<template> <div id="app"> <form novalidate @submit.prevent="onSubmit"> <InputName v-model="name" /> <InputEmail v-model="email" /> <button type="submit">Submit</button> </form> </div> </template> <script> import InputName from "@/components/InputName"; import InputEmail from "@/components/InputEmail"; export default { name: 'App', components: { InputName, InputEmail } } </script>
接下来使用 Composition API 定义表单功能。在组件定义中添加 setup 方法,并使用 Composition API 提供的 ref 方法声明两个状态变量 name 和 email。
而后声明一个 onSubmit 函数来处理表单提交。
src/App.vue
// 其他省略 ... import { ref } from "@vue/composition-api"; export default { name: "App", setup () { const name = ref(""); const email = ref(""); function onSubmit() { // 这里能够写提交后端的逻辑 console.log(name.value, email.value); } return { name, email, onSubmit } }, ... }
接下来,将定义 InputName 组件的功能。
在组件上使用了 v-model 指令,就和组件建立了双向绑定,在组件内部的 props 上定义 value 来接收值,这只作了一半的工做。
建立一个 setup 函数。props 和组件实例被传递到这个方法中,使咱们可以访问组件实例上的方法。
用解构的方式在第二个参数中得到 emit 方法。将须要它来完成 v-model 的双向绑定的另外一半工做,即触发 input 事件,修改绑定的值。
在此以前,声明一个状态变量 input,将绑定到咱们在模板中声明的 HTML 元素上。
该变量的值是待定义的合成函数 useInputValidator 执行后返回的值。此函数将处理全部常见的验证逻辑。
把 prop.value 传递给这个方法做为第一个参数,第二个参数是一个回调函数,接收通过验证后的输入值,在这个回调函数中触发 input 事件,修改 v-model 绑定的值,实现和父组件双向绑定的功能。
src/components/InputName.vue
<template> <div> <label> Name <input type="text" v-model="input" name="name" /> </label> </div> </template> <script> import useInputValidator from "@/features/useInputValidator"; export default { name: "InputName", props: { value: String }, setup (props, { emit }) { const { input } = useInputValidator( props.value, value => emit("input", value) ); // 绑定在元素上 return { input } } } </script>
开始建立 useInputValidator 组合函数,为此,首先建立一个 features 文件夹,而后为其建立一个模块文件。
$ mkdir src/features $ touch src/features/useInputValidator.js
在模块文件中,将导出一个函数,它须要两个参数: 从父表单接收到的值,用 startVal 接收;以及一个回调函数,用 onValidate 接收。
函数须要返回一个 input 状态变量,所以须要声明它,经过调用 ref 并提供 startVal 的值进行初始化。
在从函数返回 input 以前,观察该值的变化,并调用 onValidate回调,传入最新的 input 的值。
src/features/useInputValidator.js
import { ref, watch } from "@vue/composition-api"; export default function (startVal, onValidate) { let input = ref(startVal); watch(input, value => { onValidate(value); }); return { input } }
下一步添加验证器函数。对于 InputName 组件,只有一个验证规则 minLength,确保输入是三个字符或更多。还没有建立的 InputEmail 组件将须要电子邮件验证规则。
将在 src 文件夹中建立模块 validators.js,并写这些验证器。在实际的项目中,您可能会使用第三方库。
不会详细介绍 validator 函数,可是有两件重要的事情须要注意:
src/validators.js
const minLength = min => { return input => input.length < min ? `Value must be at least ${min} characters` : null; }; const isEmail = () => { const re = /\S+@\S+\.\S+/; return input => re.test(input) ? null : "Must be a valid email address"; } export { minLength, isEmail };
回到上面的组合函数所在文件 useInputValidator.js 中,咱们但愿使用须要的验证,给函数添加另外一个参数,它是一组验证函数。
在 input 监视器内部,使用数组的 map 方法调用验证函数,将 input 的当前值传递给每一个验证器方法。
返回值将在一个新的状态变量 errors 中捕获,也将返回给所在组件使用。
src/features/useInputValidator.js
export default function (startVal, validators, onValidate) { const input = ref(startVal); const errors = ref([]); watch(input, value => { errors.value = validators.map(validator => validator(value)); onValidate(value); }); return { input, errors } }
最后回到 InputName 组件,如今将为 useInputValidator 方法提供必需的三个参数。
第二个参数如今是一个验证器数组,所以让咱们在适当的地方声明一个数组,并传入 minLength 方法。
minLength 是一个工厂函数,调用并传递指定的最小长度。
如今咱们还从合成函数返回的对象获取 input 和 errors,它们都将从 setup 方法返回,以便在组件中可用。
src/components/InputName.vue
// 省略其余代码 ... import { minLength } from "@/validators"; import useInputValidator from "@/features/useInputValidator"; export default { ... setup (props, { emit }) { const { input, errors } = useInputValidator( props.value, [ minLength(3) ], value => emit("input", value) ); return { input, errors } } }
这是咱们将添加到该组件的最后一个功能。在咱们继续以前,花点时间对比一下这段代码比使用mixin可读性强得多。
首先,能够清楚地看到状态变量在哪里声明和修改,而没必要切换到单独的 mixin 模块文件。另外,不须要担忧局部变量和复合函数之间的名称冲突。
进入 InputName 组件的模板,有潜在的错误数组要显示,将其委托给一个称为 ErrorDisplay 的组件来显示错误。
src/components/InputName.vue
<template> <div> <label> Name <input type="text" v-model="input" name="name" /> </label> <ErrorDisplay :errors="errors" /> </div> </template> <script> ... import ErrorDisplay from "@/components/ErrorDisplay"; export default: { ... components: { ErrorDisplay } } </script>
ErrorDisplay 组件根据业务须要,能够本身定制。
这就是咱们基于Composition API 写的表单的基本功能。本教程的目标是建立清晰且可扩展的表单代码,经过定义 InputEmail 组件,来证实咱们已经作到了这一点。
src/components/InputEmail
<template> <div> <label> Email <input type="email" v-model="input" name="email" /> </label> <ErrorDisplay v-if="input" :errors="errors" /> </div> </template> <script> import useInputValidator from "@/features/useInputValidator"; import { isEmail } from "@/validators"; import ErrorDisplay from "./ErrorDisplay"; export default { name: "InputEmail", props: { value: String }, setup (props, { emit }) { const { input, errors } = useInputValidator( props.value, [ isEmail() ], value => emit("input", value) ); return { input, errors } }, components: { ErrorDisplay } } </script>
原文:https://vuejsdevelopers.com/2...
参考:https://css-tricks.com/how-th...
github博客地址:https://github.com/WYseven/bl...。
若是对你有帮助,请关注【前端技能解锁】: