如何在 Vue 中创建数据驱动的用户界面
基础知识
道具和事件怎么样?
您需要预先了解所有道具吗?
那么表格呢?
数据绑定
使生成器可重复使用
使用生成器
虽然我们通常知道在应用程序中构建大多数视图时需要哪些组件,但有时我们直到运行时才知道它们是什么。
这意味着我们需要根据应用程序状态、用户偏好设置或 API 响应来构建屏幕。一种常见的情况是创建动态表单,其中所需的问题和组件要么由 JSON 对象配置,要么字段会根据用户的回答而变化。
所有现代 JavaScript 框架都有处理动态组件的方法。这篇博文将向您展示如何在 Vue.JS 中实现这一点,它为上述场景提供了一个非常优雅且简单的解决方案。
一旦您了解使用 Vue.JS 可以多么轻松地实现这一点,您可能会受到启发并看到以前从未考虑过的动态组件应用程序!
我们必须先学会走路,然后才能跑步,因此,首先我将介绍动态组件的基础知识,然后深入研究如何使用这些概念来构建您自己的动态表单构建器。
基础知识
Vue 有一个名为 (fittingly) 的内置组件。你可以在VueJS 指南的动态组件部分<component>
查看完整的详细信息。
指南说:
“您可以使用相同的挂载点,并使用保留元素在多个组件之间动态切换,并动态绑定到其属性。”
这意味着组件之间的交换可以非常简单:
<component :is="componentType">
让我们进一步充实一下,看看到底发生了什么。我们将创建两个名为DynamicOne
和 的组件DynamicTwo
——目前 One 和 Two 是相同的,所以我就不重复它们的代码了:
<template>
<div>Dynamic Component One</div>
</template>
<script>
export default {
name: 'DynamicOne',
}
</script>
为了快速演示如何在它们之间切换,我们将在 App.vue 中设置我们的组件:
import DynamicOne from './components/DynamicOne.vue'
import DynamicTwo from './components/DynamicTwo.vue'
export default {
name: 'app',
components: {
DynamicOne, DynamicTwo
},
data() {
return {
showWhich: 'DynamicOne'
}
}
}
注意:showWhich 数据属性是 DynamicOne 的字符串值 - 这是在组件上的 components 对象中创建的属性名称。
在我们的模板中,我们将设置两个按钮来在两个动态组件之间切换:
<button @click="showWhich = 'DynamicOne'">Show Component One</button>
<button @click="showWhich = 'DynamicTwo'">Show Component Two</button>
<component :is="showWhich"></component>
单击按钮将DynamicOne
与交换DynamicTwo
。
这时,你可能会想:“那又怎么样?这很方便 - 但我也可以同样轻松地使用 v-if。”
当您意识到它的工作方式与任何其他组件一样时,这个示例就开始闪耀<component>
,并且可以与指令结合使用,例如v-for
遍历集合,或使其:is
可绑定到输入道具,数据道具或计算属性。
道具和事件怎么样?
组件并非孤立存在——它们需要一种与周围环境进行通信的方式。在 Vue 中,这通过 props 和事件实现。
您可以像任何其他组件一样在动态组件上指定属性和事件绑定,并且如果加载的组件不需要该属性,则 Vue 不会抱怨未知的属性或属性。
让我们修改一下组件来显示问候语。一个组件只接受 firstName 和 lastName,另一个组件则接受 firstName、lastName 和 title。
对于事件,我们将在 DynamicOne 中添加一个按钮,该按钮将发出一个名为“upperCase”的事件,并在 DynamicTwo 中添加一个按钮,该按钮将发出一个名为“lowerCase”的事件。
把它们放在一起,使用动态组件开始看起来像这样:
<component
:is="showWhich"
:firstName="person.firstName"
:lastName="person.lastName"
:title="person.title"
@upperCase="switchCase('upperCase')"
@lowerCase="switchCase('lowerCase')">
</component>
并不是每个属性或事件都需要在我们正在切换的动态组件上定义。
您需要预先了解所有道具吗?
此时,您可能会想,“如果组件是动态的,并且不是每个组件都需要知道每个可能的道具 - 我是否需要预先知道道具,并在模板中声明它们?”
值得庆幸的是,答案是否定的。Vue 提供了一个快捷方式,你可以使用 将对象的所有键绑定到组件的 props 上v-bind
。
这将模板简化为:
<component
:is="showWhich"
v-bind="person"
@upperCase="switchCase('upperCase')"
@lowerCase="switchCase('lowerCase')">
</component>
那么表格呢?
现在我们有了动态组件的构建块,我们可以开始在其他 Vue 基础之上构建表单生成器。
让我们从基本的表单模式开始——一个描述表单字段、标签、选项等信息的 JSON 对象。首先,我们将讨论以下内容:
- 文本和数字输入字段
- 选择列表
起始模式如下:
schema: [{
fieldType: "SelectList",
name: "title",
multi: false,
label: "Title",
options: ["Ms", "Mr", "Mx", "Dr", "Madam", "Lord"],
},
{
fieldType: "TextInput",
placeholder: "First Name",
label: "First Name",
name: "firstName",
},
{
fieldType: "TextInput",
placeholder: "Last Name",
label: "Last Name",
name: "lastName",
},
{
fieldType: "NumberInput",
placeholder: "Age",
name: "age",
label: "Age",
minValue: 0,
},
]
非常简单 - 标签、占位符等 - 对于选择列表,是可能的选项列表。
对于此示例,我们将保持这些组件的实现简单。
文本输入.vue
<template>
<div>
<label>{{label}}</label>
<input type="text"
:name="name"
placeholder="placeholder">
</div>
</template>
<script>
export default {
name: 'TextInput',
props: ['placeholder', 'label', 'name']
}
</script>
选择列表.vue
<template>
<div>
<label>{{label}}</label>
<select :multiple="multi">
<option v-for="option in options"
:key="option">
{{option}}
</option>
</select>
</div>
</template>
<script>
export default {
name: 'SelectList',
props: ['multi', 'options', 'name', 'label']
}
</script>
要根据此模式生成表单,请添加以下内容:
<component v-for="(field, index) in schema"
:key="index"
:is="field.fieldType"
v-bind="field">
</component>
其结果如下:
数据绑定
如果生成了一个表单,但没有绑定数据,这还有用吗?可能不行。我们目前正在生成一个表单,但没有办法绑定数据。
您的第一反应可能是向模式添加一个值属性,并在组件中v-model
像这样使用:
<input type="text"
:name="name"
v-model="value"
:placeholder="placeholder">
这种方法存在一些潜在的陷阱,但我们最关心的是 Vue 会给我们一个错误/警告:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
found in
---> <TextInput> at src/components/v4/TextInput.vue
<FormsDemo> at src/components/DemoFour.vue
<App> at src/App.vue
<Root>
虽然 Vue 确实提供了一些辅助函数来简化组件状态的双向绑定,但该框架仍然使用单向数据流。我们尝试在组件内部直接修改父级数据,因此 Vue 发出了警告。
更仔细地看一下 v-model,它并没有那么多神奇之处,所以让我们按照 [Vue 表单输入组件指南]( https://vuejs.org/v2/guide/components-custom-events.html#Binding-Native-Events-to-Components_ ) 中的说明进行分解。
<input v-model="something">
类似于:
<input
v-bind:value="something"
v-on:input="something = $event.target.value">
随着魔法的揭晓,我们想要实现的目标是:
- 让父组件向子组件提供值
- 让父母知道值已更新
我们通过绑定:value
并发出@input
事件来通知父级某些事情已经发生变化来实现这一点。
让我们看看我们的 TextInput 组件:
<div>
<label>{{ label }}</label>
<input
type="text"
:name="name"
:value="value"
@input="$emit('input',$event.target.value)"
:placeholder="placeholder"
/>
</div>
由于父组件负责提供值,因此它也负责处理与其自身组件状态的绑定。为此,我们可以在组件标签上使用 v-model :
FormGenerator.vue
<component v-for="(field, index) in schema"
:key="index"
:is="field.fieldType"
v-model="formData[field.name]"
v-bind="field">
</component>
注意我们是如何使用的v-model="formData[field.name]"
。我们需要在 data 属性上提供一个对象来实现这一点:
export default {
data() {
return {
formData: {
firstName: 'Evan'
},
}
我们可以将对象留空,或者如果我们想要设置一些初始字段值,我们可以在这里指定它们。
现在我们已经完成了表单的生成,很明显这个组件承担了相当多的责任。
虽然这不是复杂的代码,但如果表单生成器本身是一个可重复使用的组件就好了。
使生成器可重复使用
对于这个表单生成器,我们希望将模式作为道具传递给它,并能够在组件之间设置数据绑定。
当使用生成器时,模板变成:
GeneratorDemo.vue
<form-generator :schema="schema" v-model="formData">
</form-generator>
这大大简化了父组件。它只关心 FormGenerator,而不关心每个可能用到的输入类型、事件连接等等。
接下来,创建一个名为 的组件FormGenerator
。这基本上就是复制粘贴初始代码,并进行一些细微但关键的调整:
- 更改为
v-model
和:value
事件@input
处理 - 添加道具价值和架构
- 实施
updateForm
该FormGenerator
组件变为:
FormGenerator.vue
<template>
<component v-for="(field, index) in schema"
:key="index"
:is="field.fieldType"
:value="formData[field.name]"
@input="updateForm(field.name, $event)"
v-bind="field">
</component>
</template>
<script>
import NumberInput from '@/components/v5/NumberInput'
import SelectList from '@/components/v5/SelectList'
import TextInput from '@/components/v5/TextInput'
export default {
name: "FormGenerator",
components: { NumberInput, SelectList, TextInput },
props: ['schema', 'value'],
data() {
return {
formData: this.value || {}
};
},
methods: {
updateForm(fieldName, value) {
this.$set(this.formData, fieldName, value);
this.$emit('input', this.formData)
}
}
};
</script>
由于该formData
属性不知道我们可以传入的每个可能的字段,this.$set
因此我们希望使用 Vue 的反应系统来跟踪任何更改,并允许 FormGenerator 组件跟踪其自己的内部状态。
现在我们有一个基本的、可重复使用的表单生成器。
使用生成器
<template>
<form-generator :schema="schema" v-model="formData">
</form-generator>
</template>
<script>
import FormGenerator from '@/components/v5/FormGenerator'
export default {
name: "GeneratorDemo",
components: { FormGenerator },
data() {
return {
formData: {
firstName: 'Evan'
},
schema: [{ /* .... */ },
}
</script>
现在你已经了解了表单生成器如何利用 Vue 中动态组件的基础知识来创建一些高度动态、数据驱动的 UI,
我鼓励您在GitHub上试用此示例代码,或在 [CodeSandbox] 上进行实验。如果您有任何疑问或想与我们探讨,请随时联系我们,在下方评论或通过以下方式联系我们:
注:本文最初于 2018 年 3 月 7 日发表在rangle.io博客上
文章来源:https://dev.to/eschultz/how-to-create-data-driven-user-interfaces-in-vue-3k6c