Vue3 仿知乎项目实战(二) —— 抽象验证规则、vue2/3 自定义组件的 v-model 对比、使用 $attrs 支持默认属性、表单插槽、自定义组件父子传值 mitt

目录

ValidateInput - 抽象验证规则

Vue2 自定义组件中的 v-model

Vue3 自定义组件中的 v-model

ValidateInput - 支持 v-model【重要】

ValidateInput - 使用 $attrs 支持默认属性

ValidateForm - 需求分析、插槽

ValidateForm - 自定义组件之间的传值 mitt


ValidateInput - 抽象验证规则

  • RuleProp:单条验证规则,包含了:
  1. 类型(比如 空值检测、邮箱检测、密码检测等验证规则类型)
  2. 类型对应的错误时的提示信息

 

  • RulesProp:验证规则组,一个输入框可能不止一个验证规则,而输入框就应该接受 验证规则组类型的参数 rules: Array as PropType<RulesProp>
  • 比如:判断是否为空并且符合邮箱格式,这就是两个验证规则

 

  • inputRef:输入框的绑定信息,包含了:
  • 输入框值 val,输入是否正确 error,对应报错信息 message

 

  • validateInput:验证规则具体的方法,每个输入框不一定会传入验证规则 rules,当传入的话,要执行所有规则的判断结果,没传入的话,就默认通过返回 true
  • 根据传入的规则,在上述方法中更改 inputRef 输入框的绑定信息
    <!-- 如果有误时添加的类名: :class="{'is-invalid': inputRef.error}" -->
    <!-- 失去焦点时执行的验证: @blur="validateInput" -->
    <input
      v-if="tag !== 'textarea'"
      class="form-control"
      :class="{'is-invalid': inputRef.error}"
      @blur="validateInput"
    >
    <!-- 如果有误时,输入框下方渲染的错误信息 -->
    <span v-if="inputRef.error" class="invalid-feedback">{{inputRef.message}}</span>

// 邮箱正则
const emailReg = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/

// 单条验证规则类型
interface RuleProp {
  // 验证类型
  type: 'required' | 'email' | 'custom';
  // 不通过验证时的错误提示
  message: string;
  // 是否通过验证
  validator?: () => boolean;
}

// 验证规则组,是由多个 验证规则类型 构成的数组
// 一个输入框可以有斗个 验证规则类型,比如不能为空,并且输入的内容符合邮箱格式
export type RulesProp = RuleProp[]

export default defineComponent({
  // 输入框接受参数
  props: {
    //  验证规则组
    rules: Array as PropType<RulesProp>,
  },
  setup(props, context) {
    const inputRef = reactive({
      // 输入框当前的值
      val: '',
      // 输入框内容是否有误
      error: false,
      // 输入框有误时的报错信息
      message: ''
    })
    // 验证规则们的具体方法
    const validateInput = () => {
      // 如果传入的参数中包含了验证规则,返回是否全部通过 allPassed
      if (props.rules) {
        // every():只要有一个 false,就会返回 false,并停止向下进行
        const allPassed = props.rules.every(rule => {
          // 默认为验证通过
          let passed = true
          // 当前输入框的错误提示信息,绑定上传入规则的错误提示信息
          inputRef.message = rule.message
          // 判断当前验证规则的类型
          switch (rule.type) {
            case 'required':
              // 更改 passed值,不为空时才是true
              passed = (inputRef.val.trim() !== '')
              break
            case 'email':
              passed = emailReg.test(inputRef.val)
              break
            default:
              break
          }
          return passed
        })
        // 输入框是否包含错误,由当前输入框值是否通过全部的验证规则决定
        inputRef.error = !allPassed
        return allPassed
      }
      // 如果传入的参数中不包含验证规则,默认返回通过验证 true
      return true
    }
    return {
      inputRef,
      validateInput
    }
  }
})
  • 使用示例:
<div class="mb-3">
  <label class="form-label">邮箱地址</label>
  <validate-input :rules="emailRules"></validate-input>
</div>

// 验证规则组
const emailRules: RulesProp = [
  { type: 'required', message: '电子邮箱地址不能为空' },
  { type: 'email', message: '请输入正确的电子邮箱格式' }
]

Vue2 自定义组件中的 v-model

  • vue2 自定义组件实现 v-model 的方法:
  • 给组件绑定 :value、绑定 @input,触发 input 时,emit 当前值发送给父组件,进而实现双向绑定
// 组件
<template>
  <input type="text" :value="value" @input="updateInput">
</template>

<script>
  props: {
    value: String
  },
  methods: {
    updateInput(e) {
      // 把要改变的值,发送出去
      this.$emit('input', e.target.value);
    }
  }
</script>

// 使用组件
<my-component v-model='val'></my-component>
<script>
  data() {
    val: ''
  }
</script>
  • 如果是单选框、复选框,则又不相同,区别在于:
  •   model: {
        prop: "checked", // 需要绑定的属性
        event: "change", // 需要触发的事件
      }
// 组件
<template>
  <input type="checkbox" :checked="checked" @change="updateInput">
</template>

<script>
  props: {
    checked: Boolean
  },
  model: {
    prop: "checked", // 需要绑定的属性
    event: "change", // 需要触发的事件
  }
  methods: {
    updateInput(e) {
      // 把要改变的值,发送出去
      this.$emit('change', e.target.checked);
    }
  }
</script>

// 使用组件
<my-component v-model='checked'></my-component>
<script>
  data() {
    checked: false
  }
</script>
  • vue2 通过上述两种方式,实现自定义组件 v-model,十分繁琐,而且只能双向绑定一个值,使用了 value 就不能用 checked

Vue3 自定义组件中的 v-model

ValidateInput - 支持 v-model【重要】

  • props 接受 vue3自定义组件中,需要双向绑定的值 modelValue
  • update:modelValue 是 vue3自定义组件中,需要触发实现双向绑定的事件
<template>
  <div class="validate-input-container pb-3">
    <input :value="inputRef.val" @input="updateValue">
  </div>
</template>

<script lang="ts">
  props: {
    modelValue: String // vue3自定义组件中,需要双向绑定的值 modelValue
  },
  const inputRef = reactive({
    val: props.modelValue || '',
    error: false,
    message: ''
  })
  const updateValue = (e: KeyboardEvent) => {
    // 获取当前输入的值
    const targetValue = (e.target as HTMLInputElement).value
    // 重新赋值输入框绑定的值
    inputRef.val = targetValue
    // vue3自定义组件中,需要触发实现双向绑定的事件 update:modelValue
    context.emit('update:modelValue', targetValue)
  }
</script>

ValidateInput - 使用 $attrs 支持默认属性

  • input 有自带的属性 type、placeholder 等等,ValidateInput 是 input 构成的组件
  • 直接在 ValidateInput 组件上传入 placeholder 属性的话,该属性不会添加到 input上,而是添加到 ValidateInput 组件的根元素div上

 

  • v-bind="$attrs" 存储了所有的 添加在组件上的 默认属性
  • 为了让  type、placeholder 等渲染到 input 上,需要实现以下步骤:
  1. inheritAttrs: false:禁用 attribute 继承,在组件上添加的 非props属性(type、placeholder等默认属性) 就不会渲染到组件根节点
  2. 给组件中需要添加 默认属性的元素 添加  v-bind="$attrs",比如:<input v-bind="$attrs">,这样 组件上添加的默认属性就会渲染到 input 上
// ValidateInput 组件内部
  // 添加 v-bind="$attrs",在组件上添加的 非props属性 就会渲染到当前元素上
  <input v-bind="$attrs">

  // 禁用 attribute 继承,在组件上添加的 非props属性 就不会渲染到组件根节点
  inheritAttrs: false,

// 使用组件
  // placeholder="请输入昵称" type="text" 本来会渲染到组件根节点div上,此时因为上面的操作,添加到了input上
  <validate-input placeholder="请输入昵称" type="text" />

ValidateForm - 需求分析、插槽

  • 分析:Vue3 仿知乎项目实战(二) —— 抽象验证规则、vue2/3 自定义组件的 v-model 对比、使用 $attrs 支持默认属性、表单插槽、自定义组件父子传值 mitt

 

  • 提交按钮有默认值,也可以自定义,因此考虑插槽,下面是方法:
  • 组件内使用:通过 name 指定插槽名字
  • <slot name="header">
  • 调用组件使用插槽:通过 template 自定义 所调用组件内部的自定义部分,通过 #xx / v-slot="xx" 指定对应插槽
  • <validate-form><template #header></template></validate-form>

 

  • 表单内部就是:自定义内容区(使用 validate-input 组件搭建)+ 自定义按钮区(如果不自定义,则有默认按钮显示)
  • 表单本身点击提交区域后,应该触发一个事件,让调用表单的组件监听这个事件,进而接受表单传递的值
  • 思路:给表单提交按钮绑定 @click.prevent="submitForm",此事件负责 传递父组件要监听的事件,以及表单的验证结果
  • 通过 数组 定义事件:emits: ['form-submit'],父组件要监听这些事件,进而获取表单组件传递的值

 

  • 父组件中调用 Form表单组件 后的使用方式:
  • <validate-form @form-submit="onFormSubmit">
  • const onFormSubmit = (result:boolean) => { console.log('123', result) } // result 就是接受的表单验证结果
<template>
  <form class="validate-form-container">
    // 表单内容区域
    <slot name="default"></slot>
    // 表单自定义按钮区域,此处点击后,提交事件,传递表单验证结果
    <div class="submit-area" @click.prevent="submitForm">
      <slot name="submit">
        <button type="submit" class="btn btn-primary">提交</button>
      </slot>
    </div>
  </form>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  // 通过数组定义要提交的事件
  emits: ['form-submit'],
  setup(props, context) {
    const submitForm = () => {
      // 要提交的事件,以及要传递的值(表单验证结果,此处用假的)
      context.emit('form-submit', true)
    }
    return {
      submitForm
    }
  }
})
</script>

ValidateForm - 自定义组件之间的传值 mitt

  • validate-form 组件里,包含了 validate-input 组件,每个 validate-input 都有自己的验证结果
  • 我们可以在 validate-form 中定义一个数组 validateFuncArr,用来将所有 validate-input 验证结果存储到这个数组中
  • 提交表单时,validate-form 组件会提交全部表单的验证结果
  • 那么如何从 validate-form 中,获取其子组件 validate-input 的值呢?

 

  • 如果只有一个子组件,可以考虑 $on 发送事件
  • 但是 validate-form 中的 slot 显然不止一个子组件,并且 $on、$off 在 vue3 中已经废除
  • 我们可以通过 mitt 实现父子组件的通讯
  • 安装 mitt:npm install --save mitt

 

  • 在 validate-form 中,导入 mitt:import mitt from 'mitt'
  • 在 validate-form 中,实例化 mitt事件监听器 emitter,并导出 ,供 validate-input 使用:export const emitter = mitt()
  • 在 validate-form 中,添加回调函数 callback,用于接收到 validate-input 传来值后,如何处理传来值
  • 在 validate-form 中,添加监听,此时 validate-form 像收音机一样等待 validate-input 发送事件及值:emitter.on('form-item-created', callback)
  • 在 validate-form 中,移除监听,emitter.off('form-item-created', callback)

 

  • 在 validate-input 中,导入validate-form 中的 实例化 mitt事件监听器 emitter:import { emitter } from './ValidateForm.vue'
  • 在 validate-input 中,把当前组件的值(验证结果)传给 validate-form 组件:emitter.emit('form-item-created', validateInput【这是个方法】)

 

  • 在 validate-form 中,添加数组 funcArr,用于存储一堆 validateInput函数
  • 在 validate-form 中,改写回调函数 callback,funcArr.push(func),填充 funcArr 数组

 

  • 在 validate-form 中,执行提交事件时,要提交 接收到的 validate-input 的结果,也就是要执行 funcArr 中的全部验证函数,并获取结果,再提交
  • 通过 map 执行全部验证函数 funcArr.map(func => func()),保证所有错误都被渲染出来,此时获得的是 装满布尔值的数组
  • 再通过 every 确定 全部验证函数是否都通过,funcArr.map(func => func()).every(result => result)
  • 如果不用 map 只用 every,funcArr.every( func => func()) 会导致错误只渲染了一个,就停止了,因为 every 遇到 false后,不会执行后续操作
// ValidateForm.vue

import mitt from 'mitt'
// 该类型是 返回布尔值的函数 类型
type ValidateFunc = () => boolean
// 实例化 mitt
export const emitter = mitt()
export default defineComponent({
  emits: ['form-submit'],
  setup(props, context) {
    // 用于存放 input验证函数 的数组
    let funcArr: ValidateFunc[] = []
    // 表单提交后,要触发的事件
    const submitForm = () => {
      // 表单内部的验证结果
      // 因为 every 遇到false后会停止后续执行
      // 所以应该先用 map 让所有函数执行,得到一个装满布尔值的数组,实现报错全部显示
      // 然后再调用 every,判断表单最终结果
      const result = funcArr.map(func => func()).every(result => result)
      // 提交表单后,要提交的事件及传递的值(表单验证结果)
      context.emit('form-submit', result)
    }
    // 将监听得到的验证函数都存到一个数组中
    const callback = (func: ValidateFunc) => {
      funcArr.push(func)
    }
    // mitt事件监听器监听 事件及回调函数(他像收音机一样在等待 validate-input发送的信息)
    emitter.on('form-item-created', callback)
    onUnmounted(() => {
      // 组件卸载的时候,应该关闭监听器
      emitter.off('form-item-created', callback)
      funcArr = []
    })
    return {
      submitForm
    }
  }
})


// ValidateInput.vue
// 将事件发射出去,其实就是把验证函数发射出去
onMounted(() => {
  emitter.emit('form-item-created', validateInput)
})

 

上一篇:mysql修改密码报错:Your password does not satisfy the current policy requirements


下一篇:mysql权限