Vue进阶 [四] vue 组件化实战之通用表单组件实现

进阶会发现越往前走,知道的越多,不知道的也越多,但是通过学习,弱化知识盲区,对自己的实际开发选择会有很大的影响,也会影响变成思维。

概述

组件系统是vue的一个重要概念,因为它是一种抽象,让我们可以使用独立可抽象的组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树。所谓组件化,就是把页面拆分成多个独立的组件,每个组件依赖的 css  js  模板  图片 等资源等放在一起开发和维护,所以组件化在系统内部可复用,组件之间可以嵌套。因此组件化能提高开发效率,方便重复使用,简化调试步骤,提升项目可维护性,编译多人协同开发

组件化实战动手封装表单组件

注意

高内聚 低耦合 封装通用组件每一个组件的功能尽量单一 明确,比如 input 就实现双向数据绑定 收集数据

需求分析

首先要封装一个简单的类似Element UI form 表单的组件 根据Element form 的组成 划分三各需要自定义封装的组件,主要功能是 收集数据  并进行数据的校验。

  • ZForm  :指定数据 和 数据校验规则
  • ZFormItem:执行校验  显示对应的错误提示信息
  • ZInput :维护数据

完成了需求分析 就去解决每一个需求的功能实现吧 ,从内层到外层实现 能最快的看到效果并方便调试

代码实现

首先来看下实现后的使用如下

<template>
    <div>
        <h1>Form 自定义通用组件练习 </h1>
        <ZForm :model="userInfo"></ZForm>
        <ZFormItem label="用户名">
             <ZInput v-model="userInfo.username"></ZInput>
        </ZFormItem>
        {{userInfo.username}}
    </div>
</template>

<script>
import ZForm from "./ZForm";
import ZFormItem from "./ZFormItem";
import ZInput from "./ZInput";
export default {
  data() {
    return {
      userInfo: {
        username: "tom",
        password: ""
      },
      rules: {
        username: [{ required: true, message: "请输入用户名称" }],
        password: [{ required: true, message: "请输入密码" }]
      }
    };
  },
  components: {
    ZInput,
    ZFormItem,
    ZForm
  }
};
</script>

<style lang="scss" scoped>
</style>
\ No newline at end of file

1、ZInput :实现双向数据绑定 v-model       

自定义组件 如果想实现双向数据绑定 需要两步实现 value 属性的绑定 和 input 事件 :value   @input 实现

除了value值等 可能还会给ZInput 组件传很多其他的值 ,要设置到当前的input 组件上,比如placeholder 因此可以使用$attrs 进行属性设置,但是该属性会默认也设置到整控件的根组件上 比如 div ,可以通过设置 iinheritAttrs: false 避免其他组件的属性继承。

<template>
    <!-- <div> -->
        <!-- 自定义组件 如果想使用双向数据绑定 实现两步  value 属性绑定和input 事件  :value  @input  -->
     <input :type="type" :value="value" @input="onInput">
    <!-- </div> -->
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      default: ""
    },
    type: {
      type: String,
      default: "text"
    }
  },
  data() {
    return {};
  },
  created() {
    console.log(this.value, this.type);
  },
  methods: {
    onInput(e) {
      // 一般来说 value 数据是单项数据流 也就是监听到数据变化之后不要直接修改,要直接抛出一个事件让他去修改
      // 因此在onInput 中只需要取派发一个事件就可以了 这样便形成了单项数据流
      this.$emit("input", e.target.value);
    }
  }
};
</script>

<style lang="scss" scoped>
</style>

2、ZFormItem :实现校验 及 校验结果信息的显示 因为内部要包含 ZInput 所以 要使用插槽 slot ,同时根据实际需求也可以留出label的位置 代码如下:

<template>
    <div>
        <!-- label  -->
     <label v-if="label">{{label}}</label>
     <slot></slot>
     <!-- 校验信息提示 -->
     <p v-if="error">{{error}}</p>
    </div>
</template>

<script>
export default {
  props: {
    label: {
      type: String,
      default: ""
    }
  },
  data() {
    return {
      error: "" // error 为空说明校验通过
    };
  }
};
</script>

<style lang="scss" scoped>
</style>
\ No newline at end of file

3、ZForm 是最外层的爸爸 ,要提供两个数据 model 和 校验规则rules

<template>
    <div>
        <slot></slot>
    </div>
</template>

<script>
export default {
  data() {
    return {};
  },
  props: {
    model: {
      type: Object,
      required: true
    },
    rules: {
      type: Object
    }
  }
};
</script>

<style lang="scss" scoped>
</style>

以上代码完成了三个组件的基本准备工作,接下来是核心的业务逻辑实现  数据收集 和 数据校验

先考虑一下几个问题:

Q:最外层的数据 怎么传给子孙后代呢 ?

A:参考组件通讯的方法 https://blog.csdn.net/Sandy_zhi/article/details/108604466 可以选择provide 和 inject 注入进行实现

Q:数据传递应该传递什么呢? userInfo ? rules ?

A:经过综合分析,在使用过程中可能不仅仅是这两个数据,因此可以将组件的实例 form 传给子孙后代 ,想用什么直接从组件本身取就可以了 Element UI form 也是这样实现的。

Q:关于校验 哪个组件去提交和通知校验 

A:ZInput 去通知提交校验,因为知道数据变化的是ZInput 组件

Q:每一个ZFormItem 组件怎么知道当前的校验规则和校验内容

A:需要在ZFormItem 上设置prop 属性,然后从model 和 rules 取出内容 和 规则 再执行校验

Q:怎么实现全局校验,即在我们通常点击提交按钮的时候,要有个全局的校验 

A:需要对最外层的表单也就是ZForm 设置一个全局校验的方法 ,该方法遍历获取设置了校验的孩子 并调用孩子的校验方法。

注:demo中的校验用到了第三方的校验库 :async-validator 

核心代码如下:

① ZInput 中通知父组件进行校验

// 通知父亲级别通知校验
this.$parent.$emit("validate"); // 虽然有耦合问题

② ZFormItem  校验核心代码

    validate() {
      //规则
      //   console.log(this.from.rules[this.prop]);
      const rules = this.form.rules[this.prop];

      // 当前值
      //   console.log(this.form.model[this.prop]);
      const value = this.form.model[this.prop];
      // 校验描述对象  校验源  校验规则
      const desc = { [this.prop]: rules };

      // 创建Schema 的实例  根据描述创建 Schema
      const schema = new Schema(desc);
      return schema.validate({ [this.prop]: value }, errors => {
        if (errors) {
          console.log(errors);
          this.error = errors[0].message;
          // message 就是我们校验规则的是message
        } else {
          this.error = "";
        }
      });
    }

③ ZForm 全局校验方法 

    validate(cb) {
      // 获取所有孩子的ZFormItem 并调用每一个的validate 方法
      //[resultPromise]
      const tasks = this.$children
        .filter(item => item.prop) // 过滤掉没有prop属性的item
        .map(item => item.validate());

      // 统一处理所有Promise结果
      Promise.all(tasks)
        .then(() => cb(true))
        .catch(() => cb(false));
    }

④index 页面登录按钮吊起全局校验

 login() {
      //进行全局校验
      this.$refs["loginFormRef"].validate(valid => {
        console.log("valid valid valid " + valid);

        const notice = this.$create(Notice, {
          title: "我是小智,请求登录",
          message: valid ? "登录成功!" : "校验失败!",
          duration: 2000
        });
        notice.show();
        // if (valid) {
        //   alert("submit");
        // } else {
        //   console.log("error submit!");
        //   return false;
        // }
      });
    }

Notice 弹窗的封装

弹窗或者通知类的组件都是独立于Vue组件之外的,通常挂载于body ,它们是通过JS 动态创建的,不需要再任何组件中声明,这样做不仅能够减少耦合,位置层级等也比较好控制

主要步骤:

①定义组件

②动态创建组件

③ 将组件挂载到body上

④ 记得销毁组件,不然会出现很多垃圾组件占用内存

核心代码:

import Vue from 'vue'

function create(Component, props) {
  // 组件构造函数如何获取
  //1、Vue.extend()
  //2、render
  const vm = new Vue({
    //h 是createElement 返回VNode(虚拟dom)
    // 需要挂载才能变成真实dom
    render: (h => h(Component, {
      props,
    })),
  }).$mount() //不指定宿主元素 则会创建真实dom 但是不会追加操作

  // 获取真实dom
  document.body.appendChild(vm.$el)

  const comp = vm.$children[0]

  // 删除
  comp.remove = function () {
    document.body.removeChild(vm.$el)
    vm.$destroy()
  }
  return comp
}
export default create

Notice组件

<template>
  <div class="box" v-if="isShow">
    <h3>{{title}}</h3>
    <p class="box-content">{{message}}</p>
  </div>
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      default: ""
    },
    message: {
      type: String,
      default: ""
    },
    duration: {
      type: Number,
      default: 1000
    }
  },
  data() {
    return {
      isShow: false
    };
  },
  methods: {
    show() {
      this.isShow = true;
      setTimeout(this.hide, this.duration);
    },
    hide() {
      this.isShow = false;
      // 清除自己
      this.remove();
    }
  }
};
</script>

<style>
.box {
  position: fixed;
  width: 100%;
  top: 16px;
  left: 0;
  text-align: center;
  pointer-events: none;
  background-color: #fff;
  border: grey 3px solid;
  box-sizing: border-box;
}
.box-content {
  width: 200px;
  margin: 10px auto;
  font-size: 14px;
  padding: 8px 16px;
  background: #fff;
  border-radius: 3px;
  margin-bottom: 8px;
}
</style>

以上就完成了此次form表单的实战之旅了,主要是用到了插槽,组件间的通信 一些属性,开拓了自己的编程思维,以上代码样式和耦合性的问题还没有处理,期待下次进阶了。

完整代码地址

https://gitee.com/xiaozhidayu/vue-study-component

https://gitee.com/xiaozhidayu/vue-study-component.git

 

只要持续地努力,不懈地奋斗,就没有征服不了的东西。

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值