在项目中使用 Vue <component>
遇到了一些挑战,特别是在需要对子组件中的表单进行校验时。问题在于,通过点击 <el-aside>
标签切换子组件时,并不能自动触发表单校验,这就需要在父组件中集成对子组件表单的校验逻辑。因此写下本篇博文记录这个问题并分享相关思考以及解决方法。
本篇博文所使用到的所有代码点击此处进行跳转。
博文中的所有代码全部收集在博主的 GitHub 仓库中,相关技术栈专栏如下:
这里参照 官方文档 安装 Element,并在项目的 main.js
文件里进行导入:
import Vue from 'vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue'
Vue.config.productionTip = false
Vue.use(ElementUI);
new Vue({
render: h => h(App),
}).$mount('#app')
根据 官方文档 构建一个自定义化容器,效果如下所示:
构建容器代码如下所示:
<template>
<el-container style="height: 700px; border: 1px solid #eee">
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<el-menu :default-openeds="['1']" @select="handleSelect">
<el-submenu index="1">
<template slot="title"
><i class="el-icon-message"></i>导航一
</template>
<el-menu-item index="1-1">选项1</el-menu-item>
<el-menu-item index="1-2">选项2</el-menu-item>
</el-submenu>
<el-menu-item index="2">
<template slot="title"><i class="el-icon-menu"></i>导航二</template>
</el-menu-item>
<el-menu-item index="3">
<template slot="title">
<i class="el-icon-setting"></i>导航三
</template>
</el-menu-item>
</el-menu>
</el-aside>
<el-container>
<el-main>
<ItemOne v-if="currentIndex === '1'"></ItemOne>
<GroupOne v-else-if="currentIndex === '1-1'"></GroupOne>
<GroupTwo v-else-if="currentIndex === '1-2'"></GroupTwo>
<ItemTwo v-else-if="currentIndex === '2'"></ItemTwo>
<ItemThree v-else-if="currentIndex === '3'"></ItemThree>
</el-main>
</el-container>
</el-container>
</template>
<script>
import GroupOne from "@/components/GroupOne.vue";
import GroupTwo from "@/components/GroupTwo.vue";
import ItemOne from "@/components/ItemOne.vue";
import ItemTwo from "@/components/ItemTwo.vue";
import ItemThree from "@/components/ItemThree.vue";
export default {
name: "App",
components: {
GroupOne,
GroupTwo,
ItemOne,
ItemTwo,
ItemThree,
},
data() {
return {
currentIndex: '1'
};
},
methods: {
handleSelect(index) {
this.currentIndex = index
}
}
};
</script>
子组件模板代码如下所示:
<template>
<p>导航一</p>
</template>
<script>
export default {
name: "ItemOne"
}
</script>
<component>
标签优化代码当构建容器组件时,我们通常希望根据不同的条件,动态地渲染不同的子组件。
<el-container>
<el-main>
<ItemOne v-if="currentIndex === '1'"></ItemOne>
<GroupOne v-else-if="currentIndex === '1-1'"></GroupOne>
<GroupTwo v-else-if="currentIndex === '1-2'"></GroupTwo>
<ItemTwo v-else-if="currentIndex === '2'"></ItemTwo>
<ItemThree v-else-if="currentIndex === '3'"></ItemThree>
</el-main>
</el-container>
在上述代码中,使用了 if-else
结构来根据 currentIndex
的值选择不同的子组件进行展示。虽然这种方法可以实现功能,但随着子组件数量的增加,代码会变得冗长且难以维护。
为了优化这段代码,我们可以引入一个用于渲染动态组件或元素的 “元组件”:<component>
,这是一个对象映射的方式,使代码更加简洁和易于管理,详细原理见官方文档。
下面是详细的优化步骤:
data()
中定义一个包含组件名称与对应索引关系的映射对象 componentMap
,代码如下所示:data() {
return {
currentIndex: '1',
componentMap: {
'1': 'ItemOne',
'1-1': 'GroupOne',
'1-2': 'GroupTwo',
'2': 'ItemTwo',
'3': 'ItemThree'
}
};
},
currentComponent
,代码如下所示:computed:{
currentComponents() {
return this.componentMap[this.currentIndex]
}
},
<el-container>
<el-main>
<component :is="currentComponents"></component>
</el-main>
</el-container>
我们以 导航二 ItemTwo
为例,创建一个表单 Form,效果如下所示:
如果直接点击 “提交” 按钮,即使还有选项没有填写,表单也会被直接提交,效果如下所示:
因此我们需要进行表单验证,设置数据校验规则,在防止用户犯错的前提下,尽可能让用户更早地发现并纠正错误。
Form 组件提供了表单验证的功能,只需要通过 rules
属性传入约定的验证规则,并将 Form-Item 的 prop
属性设置为需校验的字段名即可。校验规则参见 async-validator。
下面是详细的验证步骤:
data()
中定义一个表单规则 rules
,代码如下所示:data() {
return {
...,
rules: {
name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
sex: [{ required: true, message: "请选择性别", trigger: "change" }],
food: [
{ required: true, message: "请选择喜欢吃的食物", trigger: "change" },
],
},
}
}
<el-form ref="form" :model="form" :rules="rules">
<el-form-item label="姓名" prop="name">
...
</el-form-item>
...
<el-form>
methods: {
submitForm() {
this.$refs.form.validate((valid) => {
if (valid) {
...
}
});
},
},
最终效果如下所示:
在介绍父组件验证子组件表单之前,需要了解一个前置知识:父组件如何调用子组件的方法。
接下来我们以 App.vue
作为父组件,ItemThree.vue
作为子组件进行介绍父组件如何调用子组件的方法。
1、构建子组件页面,代码如下所示:
<template>
<div>
<h1>导航三</h1>
<p>
响应内容: <span style="color: #e86666">{{ msg }}</span>
</p>
</div>
</template>
<script>
export default {
name: "ItemThree",
data() {
return {
msg: "当前没有响应内容!",
};
},
};
</script>
效果如下所示:
2、编写一个子组件方法,可以更改 “响应内容”,代码如下所示:
methods: {
changeMsg(owner) {
this.msg = `${owner} 组件改变了响应内容!`
}
}
效果如下所示:
3、父组件通过 ref
属性来调用子组件的方法。
ref
属性,代码如下所示:<component :is="currentComponents" ref="child"></component>
handleClick() {
this.$refs.child.changeMsg("Parent");
},
效果如下所示:
1、在子组件中创建一个校验方法 handleValidForm()
,代码如下所示:
handleValidForm() {
let flag = false
this.$refs.form.validate((valid) => {
if (valid) {
flag = true
this.submitForm()
}
});
return flag
},
上述代码先对表单进行校验,若校验通过则触发表单提交,并返回一个标识位 flag
,用于标识表单校验是否通过。
2、在父组件中调用此方法,通过获取到的标识位 flag
来判断表单是否校验通过,代码如下所示:
handleSelect(index) {
if (this.$refs.child.handleValidForm())
this.currentIndex = index;
}
上述代码表示如果校验通过,则实现子组件的切换,否则不做任何操作。
需要注意的是,每个被 <componet>
所使用的子组件都需要具有 handleValidForm()
方法,否则会出现报错:
vue.runtime.esm.js:4427 [Vue warn]: Error in v-on handler: "TypeError: this.$refs.child.handleValidForm is not a function"
3、效果如下所示: