实现表单组件

需求分析

  • 实现KForm
    • 指定数据model、校验规则rules
    • 校验validate()
  • KformItem
    • label标签添加:label
    • prop属性名称
    • 执行校验validate()
    • 显示错误信息
  • KInput
    • 维护数据v-model
    • 图标、反馈

KInput

v-model语法糖

  1. <input v-model="test" />
  2. <input v-bind:value="test" @input="test= $event.target.value" />

v-bind=”$attrs”: 将调用组件时的组件标签上绑定的非props的特性(class和style除外)向下传递。
在子组件中应当添加inheritAttrs: false( 避免父作用域的不被认作props的特性绑定应用在子组件的根元素上 )。

  1. <template>
  2. <div>
  3. <!-- 自定义组件双向绑定::value @input -->
  4. <!-- v-bind="$attrs"展开$attrs -->
  5. <input :type="type" :value="value" @input="onInput" v-bind="$attrs">
  6. </div>
  7. </template>
  8. <script>
  9. export default {
  10. inheritAttrs: false, // 设置为false避免设置到根元素上
  11. props: {
  12. value: {
  13. type: String,
  14. default: ''
  15. },
  16. type: {
  17. type: String,
  18. default: 'text'
  19. }
  20. },
  21. methods: {
  22. onInput(e) {
  23. // 派发一个input事件即可
  24. this.$emit('input', e.target.value)
  25. // 通知父级执行校验
  26. this.$parent.$emit('validate')
  27. // this.dispatch('KFormItem','validate');
  28. }
  29. },
  30. }
  31. </script>

KFormItem

  1. <template>
  2. <div>
  3. <!-- label -->
  4. <label v-if="label">{{label}}</label>
  5. <!-- 显示内部表单元素 -->
  6. <slot></slot>
  7. <!-- 校验信息显示 -->
  8. <p v-if="error">{{error}}</p>
  9. </div>
  10. </template>
  11. <script>
  12. // Asyc-validator
  13. import Schema from "async-validator";
  14. export default {
  15. inject: ["form"],
  16. data() {
  17. return {
  18. error: "" // error是空说明校验通过
  19. };
  20. },
  21. props: {
  22. label: {
  23. type: String,
  24. default: ""
  25. },
  26. prop: {
  27. type: String
  28. }
  29. },
  30. mounted() {
  31. this.$on("validate", () => {
  32. this.validate();
  33. });
  34. if(this.prop){
  35. //派发事件通知KForm,新增一个KFromItem实例
  36. this.dispatch('KForm','kkb.form.addField',[this])
  37. }
  38. },
  39. methods: {
  40. validate() {
  41. // 规则
  42. const rules = this.form.rules[this.prop];
  43. // 当前值
  44. const value = this.form.model[this.prop];
  45. // 校验描述对象
  46. const desc = { [this.prop]: rules };
  47. // 创建Schema实例
  48. const schema = new Schema(desc);
  49. //返回promise,全局可以统一处理
  50. return schema.validate({ [this.prop]: value }, errors => {
  51. if (errors) {
  52. this.error = errors[0].message;
  53. } else {
  54. // 校验通过
  55. this.error = "";
  56. }
  57. });
  58. }
  59. }
  60. };
  61. </script>

KForm

  1. <template>
  2. <div>
  3. <slot></slot>
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. componentName:'KForm',
  9. provide() {
  10. return {
  11. form: this //传递表单组件的实例
  12. };
  13. },
  14. props: {
  15. model: {
  16. type: Object,
  17. required: true
  18. },
  19. rules: {
  20. type: Object
  21. }
  22. },
  23. created(){
  24. this.fields=[]
  25. this.$on('kkb.form.addField'),item=>{
  26. this.fields.push(item)
  27. })
  28. },
  29. methods: {
  30. validate(cb) {
  31. // 获取所有孩子KFormItem
  32. // [resultPromise]
  33. const tasks = this.$children
  34. .filter(item => item.prop) // 过滤掉没有prop属性的Item
  35. .map(item => item.validate());
  36. //const tasks = this.fields.map(item=>item.valodate())
  37. // 统一处理所有Promise结果
  38. Promise.all(tasks)
  39. .then(() => cb(true))
  40. .catch(() => cb(false));
  41. }
  42. }
  43. };
  44. </script>

数据校验

  • Input通知校验

    1. onInput(e) {
    2. // ...
    3. // $parent指FormItem
    4. this.$parent.$emit('validate');
    5. }

    FormItem监听校验通知,获取规则并执行校验

    1. inject: ['form'], // 注入
    2. mounted() {// 监听校验事件
    3. this.$on("validate", () => {
    4. this.validate();
    5. });
    6. },
    7. methods: {
    8. validate() {
    9. // 获取对应FormItem校验规则
    10. console.log(this.form.rules[this.prop]);
    11. // 获取校验值
    12. console.log(this.form.model[this.prop]); }
    13. }
  • 安装async-validator: npm i async-validator -S

  • 表单全局验证,为Form提供validate方法


实现通知组件

  1. this.$create(
  2. Notice,
  3. {
  4. title: '社会你杨哥喊你来搬砖',
  5. message: '提示信息',
  6. duration: 1000
  7. }).show();

create函数

传入一个组件配置,创建它的实例,并且将它挂载到body上

  1. import Vue from 'vue'
  2. function create(Component, props) {
  3. // 组件构造函数如何获取?
  4. // 1.Vue.extend()
  5. // 2.render
  6. const vm = new Vue({
  7. // h是createElement, 返回VNode,是虚拟dom
  8. // 需要挂载才能变成真实dom
  9. render: h => h(Component, {props}),
  10. }).$mount() // 不指定宿主元素,则会创建真实dom,但是不会追加操作
  11. // 获取真实dom
  12. document.body.appendChild(vm.$el)
  13. const comp = vm.$children[0]
  14. // 删除
  15. comp.remove = function() {
  16. document.body.removeChild(vm.$el)
  17. vm.$destroy()
  18. }
  19. return comp
  20. }
  21. export default create

另一种创建组件实例的方式: Vue.extend(Component)

通知组件

  1. <template>
  2. <div class="box" v-if="isShow">
  3. <h3>{{title}}</h3>
  4. <p class="box-content">{{message}}</p>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. props: {
  10. title: {
  11. type: String,
  12. default: ""
  13. },
  14. message: {
  15. type: String,
  16. default: ""
  17. },
  18. duration: {
  19. type: Number,
  20. default: 1000
  21. }
  22. },
  23. data() {
  24. return {
  25. isShow: false
  26. };
  27. },
  28. methods: {
  29. show() {
  30. this.isShow = true;
  31. setTimeout(this.hide, this.duration);
  32. },
  33. hide() {
  34. this.isShow = false;
  35. // 清除自己
  36. this.remove();
  37. }
  38. }
  39. };
  40. </script>

测试

  1. <template>
  2. <div>
  3. <!-- KForm -->
  4. <KForm :model="model" :rules="rules" ref="loginForm">
  5. <KFormItem label="用户名" prop="username">
  6. <KInput v-model="model.username" placeholder="请输入用户名"></KInput>
  7. </KFormItem>
  8. <KFormItem label="密码" prop="password">
  9. <KInput v-model="model.password" placeholder="请输入密码"></KInput>
  10. </KFormItem>
  11. <KFormItem>
  12. <button @click="login">登录</button>
  13. </KFormItem>
  14. </KForm>
  15. </div>
  16. </template>
  17. <script>
  18. import ElementTest from "@/components/form/ElementTest.vue";
  19. import KInput from "@/components/form/KInput.vue";
  20. import KFormItem from "@/components/form/KFormItem.vue";
  21. import KForm from "@/components/form/KForm.vue";
  22. import create from '@/utils/create'
  23. import Notice from '@/components/Notice.vue';
  24. export default {
  25. components: {
  26. ElementTest,
  27. KInput,
  28. KFormItem,
  29. KForm
  30. },
  31. data() {
  32. return {
  33. model: {
  34. username: "tom",
  35. password: ""
  36. },
  37. rules: {
  38. username: [{ required: true, message: "请输入用户名" }],
  39. password: [{ required: true, message: "请输入密码" }]
  40. }
  41. };
  42. },
  43. methods: {
  44. login() {
  45. this.$refs.loginForm.validate(isValid => {
  46. // this.$notice({})
  47. // 创建notice实例
  48. create(Notice, {
  49. title: '村长喊你来搬砖',
  50. message: isValid ? '请求登录': '校验失败',
  51. duration: 3000
  52. }).show()
  53. // if (isValid) {
  54. // // 合法
  55. // console.log("request login");
  56. // } else {
  57. // alert("校验失败!");
  58. // }
  59. });
  60. }
  61. }
  62. };
  63. </script>
  64. <style scoped>
  65. </style>

作业

image.png

1.

  • mixin emitter.js
  • 设置componentName
  • dispatch()

    1. function broadcast(componentName, eventName, params) {
    2. this.$children.forEach(child => {
    3. var name = child.$options.componentName;
    4. if (name === componentName) {
    5. child.$emit.apply(child, [eventName].concat(params));
    6. } else {
    7. broadcast.apply(child, [componentName, eventName].concat([params]));
    8. }
    9. });
    10. }
    11. export default {
    12. methods: {
    13. //冒泡查找compnentName相同的组件并派发事件
    14. dispatch(componentName, eventName, params) {
    15. var parent = this.$parent || this.$root;
    16. var name = parent.$options.componentName;
    17. //向上查找直到找到相同名称的组件
    18. while (parent && (!name || name !== componentName)) {
    19. parent = parent.$parent;
    20. if (parent) {
    21. name = parent.$options.componentName;
    22. }
    23. }
    24. // 如果找到就派发事件
    25. if (parent) {
    26. parent.$emit.apply(parent, [eventName].concat(params));
    27. }
    28. },
    29. broadcast(componentName, eventName, params) {
    30. broadcast.call(this, componentName, eventName, params);
    31. }
    32. }
    33. };

    2.

  • Vue.extend

    1. const Ctor = Vue.extend(Component)
    2. const comp = new Ctor({propsData: props})
    3. comp.$mount();
    4. document.body.appendChild(comp.$el)
    5. comp.remove = () => {
    6. // 移除dom
    7. document.body.removeChild(comp.$el)
    8. // 销毁组件
    9. comp.$destroy();
    10. }

    使⽤插件进⼀步封装便于使⽤,create.js

    1. import Notice from '@/components/Notice.vue'
    2. //...
    3. export default {
    4. install(Vue) {
    5. Vue.prototype.$notice = function (options) {
    6. return create(Notice, options)
    7. }
    8. }
    9. }