Vue组件化(二)| 小册免费学

281 阅读2分钟

上一节我们为组件开发做了技术铺垫,这一节开始我们来进入实战环节

首先我们来实现一套Form组件,如果你使用过市面上比较流行的组件库,你应该知道一套Form组件应该包含这样的内容

QQ截图20210427095149-8

那么我们首先下一个用例,从用例出发,倒推组件

<f-form :model="userForm" :rules="rules">
  <f-form-item label="用户名" prop="username">
    <f-input v-model="userForm.username" placeholder="请输入用户名" />
  </f-form-item>
  <f-form-item label="密码" prop="password">
    <f-input type="password" v-model="userForm.password" placeholder="请输入密码" />
  </f-form-item>
</f-form>

<script>
export default {
  data() {
    return {
      userForm: {
        username: '',
        password: ''
      },
      rules: {
        username: [
          {required: true, message: '请输入用户名'}
        ],
        password: [
          {required: true, message: '请输入密码'}
        ]
      }
    }
  }
}
</script>

从内而外实现,从input开始,然后form-item,最后form,话不多说开搞

f-input

首先来输入框,你可能会觉着这东西很简单,但是不要忘了我们之前说的,子组件不能直接修改prop接收的值,所以赶紧抛弃你直接v-model绑定prop的想法。

如果你不关闭警告你会看到这样的警告

QQ截图20210427103333-9

这里你需要知道,其实v-model是一个语法糖,下面这两行其实作用是一样的

1<input v-model="username" />
<br/>
2<input :value="username" @input="username = $event.target.value" />
<br/>
{{username}}

运行一下看下结果

QQ录屏20210427104125-10

所以在f-input中你可以这样来实现绑定的传递

<template>
  <div>
    <input :value="value" @input="inputHandler" />
  </div>
</template>

<script>
  export default {
    props: {
      value: {
        type: String,
        default: ''
      }
    },
    methods: {
      inputHandler(e) {
        this.$emit('input', e.target.value)
      }
    }
  }
</script>

这时候再去试试,发现已经可以了,控制台也没有报警告

QQ录屏20210427104853-11

然后你又发现了一个问题,设置的placeholder没有生效,因为他被添加到f-input上,而不是f-input里面的input上

QQ截图20210427105208-12

官网API文档有这么一段

QQ截图20210427105342-13

它的作用总结下来就是,如果开启此选项,父组件绑定的属性没有被props接收的,将会存在attrs里面,可以使用vbindattrs里面,可以使用v-bind将attrs绑定在任意非根组件上,然后把我们的f-input稍加改造

<template>
  <div>
    <input :value="value" @input="inputHandler" v-bind="$attrs" />
  </div>
</template>

<script>
  export default {
    inheritAttrs: true,
    // 省略部分代码
</script>

完美,可以看到设置的密码型输入框也已经生效了

QQ录屏20210427105835-14

f-form-item

到这里就比上面的输入框稍微有点难度了,但也很简单,这里需要实现的点是

  • label文本
  • 给表单组件预留插槽
  • 按规则校验并提示错误

前两点都很简单,你应该比较畏惧第三点,其实这个也很简单,有现成的“轮子”——async-validator,作为程序员要杜绝重复造轮子😂

QQ截图20210427112200-15

想一下我们之前的用例中form-item的属性:label和prop,然后配置要接收的参数

<template>
  <div>
    <label v-if="label">{{label}}</label>
    <slot></slot>
    <p v-if="errorMessage">{{errorMessage}}</p>
  </div>
</template>

<script>
  export default {
    props: {
      label: {
        type: String,
        default: ''
      },
      prop: {
        type: String,
      }
    },
    data() {
      return {
        errorMessage: ''
      }
    },
  }
</script>

这样我们第一步和第二步都完成了,接下来就是validate了,接受的prop的作用就是为了从form组件中绑定的rules中获取需要使用的验证规则,同时也使用prop从form绑定的表单内容中获取当前表单项的值。这里可以使用provide/inject来传递数据

<script>
import Schema from 'async-validator'
  export default {
    inject: ['form'],
    props: {
      label: {
        type: String,
        default: ''
      },
      prop: {
        type: String,
      }
    },
    data() {
      return {
        errorMessage: ''
      }
    },
    methods: {
      validate() {
        const rule = this.form.rules[this.prop]
        const value = this.form.model[this.prop]
        // 获取验证规则实例
        const description = {[this.prop]: rule}
        const schema = new Schema(description)

        // 使用验证规则实例的验证方法
        return schema.validate({[this.prop]: value}, (error, field) => {
          if(error) {
            this.errorMessage = error[0].message
            console.log(`${field}验证未通过`);
          } else {
            this.errorMessage = ''
          }
        })
      }
    },
  }
</script>

然后添加验证事件的监听器,在内部表单项值修改时动态验证

mounted() {
	this.$on('validate', this.validate)
}

再顺便修改一下f-input的内容

methods: {
  inputHandler(e) {
    this.$emit('input', e.target.value)
    // 通知验证
    this.$parent.$emit('validate')
  }
}

OK~~form-item齐活

f-form

在完成了form-item和input组件之后,form组件就简简单单了,再来看一下form要做的事

  • 接收验证规则和表单数据
  • 给form-item预留插槽
  • 全局校验方法

首先我们来实现前两步,很简单,不要忘了之前form-item使用inject接收的数据,这里需要绑定上

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

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

接下来全局的校验,接受一个回调函数,回调函数的参数是一个布尔值,代表校验是否通过,使用Promise.all来执行多个验证器,只要有失败的就返回false

methods: {
  validate(cb) {
    const validators = this.$children
    .filter(item => item.prop)
    .map(item => item.validate())

    Promise.all(validators)
    .then(() => cb(true))
    .catch(() => cb(false))
  }
},

然后在修改一下用例,添加一个提交按钮,绑定全局校验

<template>
  <div>
    <f-form :model="userForm" :rules="rules" ref="formRef">
      <f-form-item label="用户名" prop="username">
        <f-input v-model="userForm.username" placeholder="请输入用户名" />
      </f-form-item>
      <f-form-item label="密码" prop="password">
        <f-input type="password" v-model="userForm.password" placeholder="请输入密码" />
      </f-form-item>
      <button @click="submit">提交</button>
    </f-form>
  </div>
</template>

<script>
  // 省略部分代码
  methods: {
    submit() {
      this.$refs.formRef.validate(valid => {
        if(valid) {
          console.log('通过验证');
        } else {
          console.log('验证失败');
        }
      })
    }
  },
}
</script>

到现在我们就已经粗略的完成了一套form组件,当然他的功能并不止于此,这只是起了个头具体功能完善也就不在这里细说了,效果展示如下,样式细节也不在这里扣了

QQ录屏20210427164449-16

本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情