掘金 后端 ( ) • 2024-04-17 21:08

theme: channing-cyan highlight: a11y-dark

前言

在项目中使用 Vue <component> 遇到了一些挑战,特别是在需要对子组件中的表单进行校验时。问题在于,通过点击 <el-aside> 标签切换子组件时,并不能自动触发表单校验,这就需要在父组件中集成对子组件表单的校验逻辑。因此写下本篇博文记录这个问题并分享相关思考以及解决方法。

q0.gif

本篇博文所使用到的所有代码点击此处进行跳转

博文中的所有代码全部收集在博主的 GitHub 仓库中,相关技术栈专栏如下:

环境准备

  • Vue2;
  • element 2.15.14;

这里参照 官方文档 安装 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')

根据 官方文档 构建一个自定义化容器,效果如下所示:

q1.gif

构建容器代码如下所示:

<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>,这是一个对象映射的方式,使代码更加简洁和易于管理,详细原理见官方文档

下面是详细的优化步骤:

  1. data() 中定义一个包含组件名称与对应索引关系的映射对象 componentMap,代码如下所示:
data() {
  return {
    currentIndex: '1',
    componentMap: {
      '1': 'ItemOne',
      '1-1': 'GroupOne',
      '1-2': 'GroupTwo',
      '2': 'ItemTwo',
      '3': 'ItemThree'
    }
  };
},
  1. 然后,通过计算属性来返回当前需要渲染的子组件,在组件中添加计算属性 currentComponent,代码如下所示:
computed:{
  currentComponents() {
    return this.componentMap[this.currentIndex]
  }
},
  1. 最后,在模板中使用这个计算属性来渲染子组件,代码如下所示:
<el-container>
  <el-main>
    <component :is="currentComponents"></component>
  </el-main>
</el-container>

单个组件表单验证

我们以 导航二 ItemTwo 为例,创建一个表单 Form,效果如下所示:

v1.png

如果直接点击 “提交” 按钮,即使还有选项没有填写,表单也会被直接提交,效果如下所示:

v2.png

因此我们需要进行表单验证,设置数据校验规则,在防止用户犯错的前提下,尽可能让用户更早地发现并纠正错误。

Form 组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即可。校验规则参见 async-validator

下面是详细的验证步骤:

  1. 在 data() 中定义一个表单规则 rules,代码如下所示:
data() {
  return {
    ...,
    rules: {
      name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
      sex: [{ required: true, message: "请选择性别", trigger: "change" }],
      food: [
        { required: true, message: "请选择喜欢吃的食物", trigger: "change" },
      ],
    },
  }
}
  1. 接着在 Form 组件中传入验证规则,代码如下所示:
<el-form ref="form" :model="form" :rules="rules">
    <el-form-item label="姓名" prop="name">
        ...
    </el-form-item>
    ...
<el-form>
  1. 最后在提交表单时,进行数据的校验,代码如下所示:
methods: {
  submitForm() {
    this.$refs.form.validate((valid) => {
      if (valid) {
        ...
      }
    });
  },
},

最终效果如下所示:

v3.png

【ItemTwo.vue】代码点击此处跳转

父组件调用子组件方法

在介绍父组件验证子组件表单之前,需要了解一个前置知识:父组件如何调用子组件的方法。

接下来我们以 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>

效果如下所示:

z1.png

2、编写一个子组件方法,可以更改 “响应内容”,代码如下所示:

methods: {
  changeMsg(owner) {
    this.msg = `${owner} 组件改变了响应内容!`
  }
}

效果如下所示:

z2.png

3、父组件通过 ref 属性来调用子组件的方法。

  1. 向子组件添加 ref 属性,代码如下所示:
<component :is="currentComponents" ref="child"></component>
  1. 编写父组件调用子组件的方法,代码如下所示:
handleClick() {
  this.$refs.child.changeMsg("Parent");
},

效果如下所示:

z3.png

父组件校验子组件表单

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、效果如下所示:

q2.gif

【整体】代码点击此处跳转

后记

以上就是 如何在 Vue <component> 切换子组件时优雅地进行 Form 表单校验 的所有内容了,希望本篇博文对大家有所帮助!欢迎大家持续关注我的博客,一起分享学习和成长的乐趣!✨

📝 上篇精讲:解决 ECharts 图表窗口自适应与数据不渲染问题

💖 我是 𝓼𝓲𝓭𝓲𝓸𝓽,期待你的关注,创作不易,请多多支持;

👍 公众号:sidiot的技术驿站

🔥 系列专栏:问题解决前端大杂烩Vue.js 打怪升级之路