useOVModel 封装,兼容 watch, computed 实现双向绑定,减少代码量

745 阅读1分钟
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
/*
目标
1. 一般使用到 v-model 就会从外面传值进来,也就是 props,
  1. 可以使用 watch 方式
  2. 可以使用 computed 方式
2. 到底是哪个字段
3. 要 emits 出去
4. const data = useOVModel(props, key, emits, {
  isWatch: true 是为 watch, false 为 computed
  deep: true 是否为深度监听
  cloned: true 是否深层拷贝
})
5. data.value
*/
import { watch, computed, ref, getCurrentInstance } from 'vue'
export interface IUseOVModelOptions<T> {
  /*
    isWatch 如果为 true,则使用 watch, 如果为 false 则使用 computed
  */
  isWatch?: boolean

  /*
    是否进行深度监听,结合 isWatch 为 true 时使用
  */
  deep?: boolean

  /*
    是否需要深度拷贝
  */
  isCloned?: boolean

  /*
    是否有默认值
  */
  defaultValue?: T

  /*
    事件名称
  */
  eventName?: string
}

/**
 * @description: 规定类型
 * @param {P} props
 * @param {K} key
 * @param {function} emit
 * @param {IUseOVModelOptions} options
 * @return {*}
 */
export function useOVModel<P extends Object, K extends keyof P, emitName extends string>(
  props: P,
  key: K,
  emit: any,
  options: IUseOVModelOptions<P[K]> = {}
) {
  // 拿到 vm
  const vm = getCurrentInstance()
  // 兼容 emit
  const _emit = emit || vm?.emit || vm?.proxy?.$emit?.bind(vm?.proxy)
  // 首先拿到 options
  const { isWatch, deep, isCloned, defaultValue, eventName } = options
  // 暂时使用 JSON.parse, JSON.stringify 来实现深度克隆
  const deepCloned = (val: any) => JSON.parse(JSON.stringify(val))
  // 如果 key 存在,则使用 key, 否则默认使用 modelValue
  const _key = (key ? key : 'modelValue') as K
  // 兼容 event
  const _event = (eventName || `update:${_key!.toString()}`) as emitName

  // 如果 key 不存在,或者是空字符串,说明外面默认是 modelValue
  if (isWatch === true) {
    // 兼容是否深度克隆
    const _defaultValue = defaultValue ? props[_key] : props[_key]
    const _data = isCloned ? deepCloned(_defaultValue) : _defaultValue
    const data = ref<any>(_data)
    // 则使用 watch 监听
    watch(
      props,
      (v) => {
        data.value = v[_key]
      },
      { deep }
    )
    _emit(_event, data[_key])
    return data
  } else {
    // 则使用 computed 监听
    return computed({
      get: () =>
        isCloned
          ? deepCloned(defaultValue ? props[_key] : props[_key])
          : defaultValue
          ? props[_key]
          : props[_key],
      set: (v) => _emit(_event, v),
    })
  }
}

用起来吧

image.png