Vue 中的 defineComponent

做者:崔静javascript

defineComponent 自己的功能很简单,可是最主要的功能是为了 ts 下的类型推到。对于一个 ts 文件,若是咱们直接写html

export default {}
复制代码

这个时候,对于编辑器而言,{} 只是一个 Object 的类型,没法有针对性的提示咱们对于 vue 组件来讲 {} 里应该有哪些属性。可是增长一层 defineComponet 的话,vue

export default defineComponent({})
复制代码

这时,{} 就变成了 defineComponent 的参数,那么对参数类型的提示,就能够实现对 {} 中属性的提示,外还能够进行对参数的一些类型推导等操做。java

可是上面的例子,若是你在 vscode 的用 .vue 文件中尝试的话,会发现不写 defineComponent 也同样有提示。这个实际上是 Vetur 插件进行了处理。git

下面看 defineComponent 的实现,有4个重载,先看最简单的第一个,这里先不关心 DefineComponent 是什么,后面细看。github

// overload 1: direct setup function
// (uses user defined props interface)
export function defineComponent<Props, RawBindings = object>( setup: ( props: Readonly<Props>, ctx: SetupContext ) => RawBindings | RenderFunction ): DefineComponent<Props, RawBindings> 复制代码

defineComponet 参数为 function, function 有两个参数 props 和 ctx,返回值类型为 RawBindings 或者 RenderFunction。defineComponet 的返回值类型为 DefineComponent<Props, RawBindings>。这其中有两个泛型 Props 和 RawBindings。Props 会根据咱们实际写的时候给 setup 第一个参数传入的类型而肯定,RawBindings 则根据咱们 setup 返回值来肯定。一大段话比较绕,写一个相似的简单的例子来看:typescript

  • 相似 props 用法的简易 demo 以下,咱们给 a 传入不一样类型的参数,define 返回值的类型也不一样。这种叫 Generic Functions数组

    declare function define<Props>(a: Props): Props const arg1:string = '123' const result1 = define(arg1) // result1string const arg2:number = 1 const result2 = define(arg2) // result2: number 复制代码
  • 相似 RawBindings 的简易 demo以下: setup 返回值类型不一样,define 返回值的类型也不一样markdown

    declare function define<T>(setup: ()=>T): T const arg1:string = '123' const resul1 = define(() => { return arg1 }) const arg2:number = 1 const result2 = define(() => { return arg2 }) 复制代码

由上面两个简易的 demo,能够理解重载1的意思,defineComponet 返回类型为DefineComponent<Props, RawBindings>,其中 Props 为 setup 第一个参数类型;RawBindings 为 setup 返回值类型,若是咱们返回值为函数的时候,取默认值 object。从中能够掌握一个 ts 推导的基本用法,对于下面的定义dom

declare function define<T>(a: T): T 复制代码

能够根据运行时传入的参数,来动态决定 T 的类型 这种方式也是运行时类型和 typescript 静态类型的惟一联系,不少咱们想经过运行时传入参数类型,来决定其余相关类型的时候,就可使用这种方式。

接着看 definComponent,它的重载2,3,4分别是为了处理 options 中 props 的不一样类型。看最多见的 object 类型的 props 的声明

export function defineComponent< // the Readonly constraint allows TS to treat the type of { required: true }
  // as constant instead of boolean.
  PropsOptions extends Readonly<ComponentPropsOptions>,
  RawBindings,
  D,
  C extends ComputedOptions = {},
  M extends MethodOptions = {},
  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
  E extends EmitsOptions = Record<string, any>,
  EE extends string = string
>(
  options: ComponentOptionsWithObjectProps<
    PropsOptions,
    RawBindings,
    D,
    C,
    M,
    Mixin,
    Extends,
    E,
    EE
  >
): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE>
复制代码

和上面重载1差很少的思想,核心思想也是根据运行时写的 options 中的内容推导出各类泛型。在 vue3 中 setup 的第一个参数是 props,这个 props 的类型须要和咱们在 options 传入的一致。这个就是在ComponentOptionsWithObjectProps中实现的。代码以下

export type ComponentOptionsWithObjectProps<
  PropsOptions = ComponentObjectPropsOptions,
  RawBindings = {},
  D = {},
  C extends ComputedOptions = {},
  M extends MethodOptions = {},
  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
  E extends EmitsOptions = EmitsOptions,
  EE extends string = string,
  Props = Readonly<ExtractPropTypes<PropsOptions>>,
  Defaults = ExtractDefaultPropTypes<PropsOptions>
> = ComponentOptionsBase<
  Props,
  RawBindings,
  D,
  C,
  M,
  Mixin,
  Extends,
  E,
  EE,
  Defaults
> & {
  props: PropsOptions & ThisType<void>
} & ThisType<
    CreateComponentPublicInstance<
      Props,
      RawBindings,
      D,
      C,
      M,
      Mixin,
      Extends,
      E,
      Props,
      Defaults,
      false
    >
  >
    
export interface ComponentOptionsBase<
  Props,
  RawBindings,
  D,
  C extends ComputedOptions,
  M extends MethodOptions,
  Mixin extends ComponentOptionsMixin,
  Extends extends ComponentOptionsMixin,
  E extends EmitsOptions,
  EE extends string = string,
  Defaults = {}
>
  extends LegacyOptions<Props, D, C, M, Mixin, Extends>,
    ComponentInternalOptions,
    ComponentCustomOptions {
      setup?: ( this: void, props: Props, ctx: SetupContext<E, Props> ) => Promise<RawBindings> | RawBindings | RenderFunction | void
    //...
  }
复制代码

很长一段,一样的先用一个简化版的 demo 来理解一下:

type TypeA<T1, T2, T3> = {
  a: T1,
  b: T2,
  c: T3
}
declare function define<T1, T2, T3>(options: TypeA<T1, T2, T3>): T1 const result = define({ a: '1', b: 1, c: {} }) // result: string 复制代码

根据传入的 options 参数 ts 会推断出 T1,T2,T3的类型。获得 T1, T2, T3 以后,能够利用他们进行其余的推断。稍微改动一下上面的 demo,假设 c 是一个函数,里面的参数类型由 a 的类型来决定:

type TypeA<T1, T2, T3> = TypeB<T1, T2>
type TypeB<T1, T2> = {
  a: T1
  b: T2,
  c: (arg:T1)=>{}
}
const result = define({
  a: '1',
  b: 1,
  c: (arg) => {  // arg 这里就被会推导为一个 string 的类型
    return arg
  }
})
复制代码

而后来看 vue 中的代码,首先 defineComponent 能够推导出 PropsOptions。可是 props 若是是对象类型的话,写法以下

props: {
   name: {
     type: String,
     //... 其余的属性
   }
}
复制代码

而 setup 中的 props 参数,则须要从中提取出 type 这个类型。因此在 ComponentOptionsWithObjectProps 中

export type ComponentOptionsWithObjectProps<
  PropsOptions = ComponentObjectPropsOptions,
  //...
  Props = Readonly<ExtractPropTypes<PropsOptions>>,
  //...
>
复制代码

经过 ExtracPropTypes 对 PropsOptions 进行转化,而后获得 Props,再传入 ComponentOptionsBase,在这个里面,做为 setup 参数的类型

export interface ComponentOptionsBase<
  Props,
  //...
>
  extends LegacyOptions<Props, D, C, M, Mixin, Extends>,
    ComponentInternalOptions,
    ComponentCustomOptions {
  setup?: ( this: void, props: Props, ctx: SetupContext<E, Props> ) => Promise<RawBindings> | RawBindings | RenderFunction | void
复制代码

这样就实现了对 props 的推导。

  • this 的做用

    在 setup 定义中第一个是 this:void 。咱们在 setup 函数中写逻辑的时候,会发现若是使用了 this.xxx IDE 中会有错误提示

    Property 'xxx' does not exist on type 'void'

    这里经过设置 this:void来避免咱们在 setup 中使用 this。

    this 在 js 中是一个比较特殊的存在,它是根据运行上上下文决定的,因此 typescript 中有时候没法准确的推导出咱们代码中使用的 this 是什么类型的,因此 this 就变成了 any,各类类型提示/推导啥的,也都没法使用了(注意:只有开启了 noImplicitThis 配置, ts 才会对 this 的类型进行推导)。为了解决这个问题,typescript 中 function 的能够明确的写一个 this 参数,例如官网的例子:

    interface Card {
      suit: string;
      card: number;
    }
    
    interface Deck {
      suits: string[];
      cards: number[];
      createCardPicker(this: Deck): () => Card;
    }
    
    let deck: Deck = {
      suits: ["hearts", "spades", "clubs", "diamonds"],
      cards: Array(52),
      // NOTE: The function now explicitly specifies that its callee must be of type Deck
      createCardPicker: function (this: Deck) {
        return () => {
          let pickedCard = Math.floor(Math.random() * 52);
          let pickedSuit = Math.floor(pickedCard / 13);
    
          return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
        };
      },
    };
    
    let cardPicker = deck.createCardPicker();
    let pickedCard = cardPicker();
    
    alert("card: " + pickedCard.card + " of " + pickedCard.suit);
    复制代码

    明确的定义出在 createCardPicker 中的 this 是 Deck 的类型。这样在 createCardPicker 中 this 下可以使用的属性/方法,就被限定为 Deck 中的。

    另外和 this 有关的,还有一个 ThisType

ExtractPropTypes 和 ExtractDefaultPropTypes

上面提到了,咱们写的 props

{
  props: {
    name1: {
      type: String,
      require: true
    },
    name2: {
      type: Number
    }
  }
}
复制代码

通过 defineComponent 的推导以后,会被转换为 ts 的类型

ReadOnly<{
  name1: string,
  name2?: number | undefined
}>
复制代码

这个过程就是利用 ExtractPropTypes 实现的。

export type ExtractPropTypes<O> = O extends object
  ? { [K in RequiredKeys<O>]: InferPropType<O[K]> } &
      { [K in OptionalKeys<O>]?: InferPropType<O[K]> }
  : { [K in string]: any }
复制代码

根据类型中清晰的命名,很好理解:利用 RrequiredKeys<O>OptionsKeys<O> 将 O 按照是否有 required 进行拆分(之前面props为例子)

{
  name1
} & {
  name2?
}
复制代码

而后每一组里,用 InferPropType<O[K]> 推导出类型。

  • InferPropType

    在理解这个以前,先理解一些简单的推导。首先咱们在代码中写

    props = {
      type: String
    }
    复制代码

    的话,通过 ts 的推导,props.type 的类型是 StringConstructor。因此第一步须要从 StringConstructor/ NumberConstructor 等 xxConstrucror 中获得对应的类型 string/number 等。能够经过 infer 来实现

    type a = StringConstructor
    type ConstructorToType<T> = T extends  { (): infer V } ? V : never
    type c = ConstructorToType<a> // type c = String
    复制代码

    上面咱们经过 ():infer V 来获取到类型。之因此能够这样用,和 String/Number 等类型的实现有关。javascript 中能够写

    const key = String('a')
    复制代码

    此时,key 是一个 string 的类型。还能够看一下 StringConstructor 接口类型表示

    interface StringConstructor {
        new(value?: any): String;
        (value?: any): string;
        readonly prototype: String;
        fromCharCode(...codes: number[]): string;
    }
    复制代码

    上面有一个 ():string ,因此经过 extends {(): infer V} 推断出来的 V 就是 string。

    而后再进一步,将上面的 a 修改为 propsOptions 中的内容,而后把 ConstructorToType 中的 infer V 提到外面一层来判断

    type a = StringConstructor
    type ConstructorType<T> = { (): T }
    type b = a extends {
      type: ConstructorType<infer V>
      required?: boolean
    } ? V : never // type b = String
    复制代码

    这样就简单实现了将 props 中的内容转化为 type 中的类型。

    由于 props 的 type 支持不少中写法,vue3 中实际的代码实现要比较复杂

    type InferPropType<T> = T extends null
      ? any // null & true would fail to infer
      : T extends { type: null | true }
        ? any 
        // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
        // 这里单独判断了 ObjectConstructor 和 BooleanConstructor
        : T extends ObjectConstructor | { type: ObjectConstructor }
          ? Record<string, any>
          : T extends BooleanConstructor | { type: BooleanConstructor }
            ? boolean
            : T extends Prop<infer V, infer D> ? (unknown extends V ? D : V) : T
    
    // 支持 PropOptions 和 PropType 两种形式
    type Prop<T, D = T> = PropOptions<T, D> | PropType<T>
    interface PropOptions<T = any, D = T> {
      type?: PropType<T> | true | null
      required?: boolean
      default?: D | DefaultFactory<D> | null | undefined | object
      validator?(value: unknown): boolean
    }
    
    export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
    
    type PropConstructor<T = any> =
      | { new (...args: any[]): T & object } // 能够匹配到其余的 Constructor
      | { (): T }  // 能够匹配到 StringConstructor/NumberConstructor 和 () => string 等
      | PropMethod<T> // 匹配到 type: (a: number, b: string) => string 等 Function 的形式
    
    // 对于 Function 的形式,经过 PropMethod 构形成了一个和 stringConstructor 类型的类型
    // PropMethod 做为 PropType 类型之一
    // 咱们写 type: Function as PropType<(a: string) => {b: string}> 的时候,就会被转化为
    // type: (new (...args: any[]) => ((a: number, b: string) => {
    // a: boolean;
    // }) & object) | (() => (a: number, b: string) => {
    // a: boolean;
    // }) | {
    // (): (a: number, b: string) => {
    // a: boolean;
    // };
    // new (): any;
    // readonly prototype: any;
    // }
    // 而后在 InferPropType 中就能够推断出 (a:number,b:string)=> {a: boolean}
    type PropMethod<T, TConstructor = any> = 
      T extends (...args: any) => any // if is function with args
      ? { 
          new (): TConstructor; 
          (): T; 
          readonly prototype: TConstructor 
        } // Create Function like constructor
      : never
    复制代码
  • RequiredKeys

    这个用来从 props 中分离出必定会有值的 key,源码以下

    type RequiredKeys<T> = {
      [K in keyof T]: T[K] extends
        | { required: true }
        | { default: any }
        // don't mark Boolean props as undefined
        | BooleanConstructor
        | { type: BooleanConstructor }
        ? K
        : never
    }[keyof T]
    复制代码

    除了明肯定义 reqruied 之外,还包含有 default 值,或者 boolean 类型。由于对于 boolean 来讲若是咱们不传入,就默认为 false;而有 default 值的 prop,必定不会是 undefined

  • OptionalKeys

    有了 RequiredKeys, OptionsKeys 就很简单了:排除了 RequiredKeys 便可

    type OptionalKeys<T> = Exclude<keyof T, RequiredKeys<T>>
    复制代码

ExtractDefaultPropTypes 和 ExtractPropTypes 相似,就不写了。

推导 options 中的 method,computed, data 返回值, 都和上面推导 props 相似。

emits options

vue3 的 options 中新增长了一个 emits 配置,能够显示的配置咱们在组件中要派发的事件。配置在 emits 中的事件,在咱们写 $emit 的时候,会做为函数的第一个参数进行提示。

对获取 emits 中配置值的方式和上面获取 props 中的类型是相似的。$emit的提示,则是经过 ThisType 来实现的(关于 ThisType 参考另一篇文章介绍)。下面是简化的 demo

declare function define<T>(props:{ emits: T, method?: {[key: string]: (...arg: any) => any} } & ThisType<{ $emits: (arg: T) => void }>):T const result = define({ emits: { key: '123' }, method: { fn() { this.$emits(/*这里会提示:arg: { key: string; }*/) } } }) 复制代码

上面会推导出 T 为 emits 中的类型。而后 & ThisType ,使得在 method 中就可使用 this.$emit。再将 T 做为 $emit 的参数类型,就能够在写 this.$emit的时候进行提示了。

而后看 vue3 中的实现

export function defineComponent< //... 省却其余的 E extends EmitsOptions = Record<string, any>, //... >( options: ComponentOptionsWithObjectProps< //... E, //... > ): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE> export type ComponentOptionsWithObjectProps< //.. E extends EmitsOptions = EmitsOptions, //... > = ComponentOptionsBase< // 定义一个 E 的泛型 //... E, //... > & {
  props: PropsOptions & ThisType<void>
} & ThisType<
    CreateComponentPublicInstance<  // 利用 ThisType 实现 $emit 中的提示
      //...
      E,
      //...
    >
  >
    
// ComponentOptionsWithObjectProps 中 包含了 ComponentOptionsBase
export interface ComponentOptionsBase<
  //...
  E extends EmitsOptions, // type EmitsOptions = Record<string, ((...args: any[]) => any) | null> | string[]
  EE extends string = string,
  Defaults = {}
>
  extends LegacyOptions<Props, D, C, M, Mixin, Extends>,
    ComponentInternalOptions,
    ComponentCustomOptions {
      //..
      emits?: (E | EE[]) & ThisType<void>  // 推断出 E 的类型
}
      
export type ComponentPublicInstance<
  //...
  E extends EmitsOptions = {},
  //...
> = {
  //...
  $emit: EmitFn<E>  // EmitFn 来提取出 E 中的 key
  //...
}
复制代码

在一边学习一边实践的时候踩到一个坑。踩坑过程:将 emits 的推导过程实现了一下

export type ObjectEmitsOptions = Record<
  string,
  ((...args: any[]) => any) | null
>
export type EmitsOptions = ObjectEmitsOptions | string[];

declare function define<E extends EmitsOptions = Record<string, any>, EE extends string = string>(options: E| EE[]): (E | EE[]) & ThisType<void> 复制代码

而后用下面的方式来验证结果

const emit = ['key1', 'key2']
const a = define(emit)
复制代码

看 ts 提示的时候发现,a 的类型是 const b: string[] & ThisType<void>,可是实际中 vue3 中写一样数组的话,提示是 const a: (("key1" | "key2")[] & ThisType<void>) | (("key1" | "key2")[] & ThisType<void>)

纠结很久,最终发现写法的不一样:用下面写法的话推导出来结果一致

define(['key1', 'key2'])
复制代码

可是用以前的写法,经过变量传入的时候,ts 在拿到 emit 时候,就已经将其类型推导成了 string[],因此 define 函数中拿到的类型就变成了 string[],而不是原始的 ['key1', 'key2']

所以须要注意:在 vue3 中定义 emits 的时候,建议直接写在 emits 中写,不要提取为单独的变量再传给 emits

真的要放在单独变量里的话,须要进行处理,使得 '[key1', 'key2'] 的变量定义返回类型为 ['key1', 'key2'] 而非 string[]。可使用下面两种方式:

  • 方式一

    const keys = ["key1", "key2"] as const; // const keys: readonly ["key1", "key2"]
    复制代码

    这种方式写起来比较简单。可是有一个弊端,keys 为转为 readonly 了,后期没法对 keys 进行修改。

    参考文章2 ways to create a Union from an Array in Typescript

  • 方式二

    type UnionToIntersection<T> = (T extends any ? (v: T) => void : never) extends (v: infer V) => void ? V : never
    type LastOf<T> = UnionToIntersection<T extends any ? () => T : never> extends () => infer R ? R : never
    type Push<T extends any[], V> = [ ...T, V]
    
    type UnionToTuple<T, L = LastOf<T>, N = [T] extends [never] ? true : false> = N extends true ? [] : Push<UnionToTuple<Exclude<T, L>>, L>
    
    declare function tuple<T extends string>(arr: T[]): UnionToTuple<T> const c = tuple(['key1', 'key2']) // const c: ["key1", "key2"] 复制代码

    首先经过 arr: T[]['key1', 'key2'] 转为 union,而后经过递归的方式, LastOf 获取 union 中的最后一个,Push到数组中。

mixins 和 extends

vue3 中写在 mixins 或 extends 中的内容能够在 this 中进行提示。对于 mixins 和 extends 来讲,与上面其余类型的推断有一个很大的区别:递归。因此在进行类型判断的时候,也须要进行递归处理。举个简单的例子,以下

const AComp = {
  methods: {
    someA(){}
  }
}
const BComp = {
  mixins: [AComp],
  methods: {
    someB() {}
  }
}
const CComp = {
  mixins: [BComp],
  methods: {
    someC() {}
  }
}
复制代码

对于 CComp 中的 this 的提示,应该有方法 someB 和 someA。为了实现这个提示,在进行类型推断的时候,须要一个相似下面的 ThisType

ThisType<{
  someA
} & {
  someB
} & {
  someC
}>
复制代码

因此对于 mixins 的处理,就须要递归获取 component 中的 mixins 中的内容,而后将嵌套的类型转化为扁平化的,经过 & 来连接。看源码中实现:

// 判断 T 中是否有 mixin
// 若是 T 含有 mixin 那么这里结果为 false,觉得 {mixin: any} {mixin?: any} 是没法互相 extends 的
type IsDefaultMixinComponent<T> = T extends ComponentOptionsMixin
  ? ComponentOptionsMixin extends T ? true : false
  : false

// 
type IntersectionMixin<T> = IsDefaultMixinComponent<T> extends true
  ? OptionTypesType<{}, {}, {}, {}, {}>  // T 不包含 mixin,那么递归结束,返回 {}
  : UnionToIntersection<ExtractMixin<T>> // 获取 T 中 Mixin 的内容进行递归

// ExtractMixin(map type) is used to resolve circularly references
type ExtractMixin<T> = {
  Mixin: MixinToOptionTypes<T>
}[T extends ComponentOptionsMixin ? 'Mixin' : never]

// 经过 infer 获取到 T 中 Mixin, 而后递归调用 IntersectionMixin<Mixin>
type MixinToOptionTypes<T> = T extends ComponentOptionsBase<
  infer P,
  infer B,
  infer D,
  infer C,
  infer M,
  infer Mixin,
  infer Extends,
  any,
  any,
  infer Defaults
>
  ? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}, Defaults & {}> &
      IntersectionMixin<Mixin> &
      IntersectionMixin<Extends>
  : never
复制代码

extends 和 mixin 的过程相同。而后看 ThisType 中的处理

ThisType<
    CreateComponentPublicInstance<
      Props,
      RawBindings,
      D,
      C,
      M,
      Mixin,
      Extends,
      E,
      Props,
      Defaults,
      false
    >
  >
export type CreateComponentPublicInstance<
  P = {},
  B = {},
  D = {},
  C extends ComputedOptions = {},
  M extends MethodOptions = {},
  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
  E extends EmitsOptions = {},
  PublicProps = P,
  Defaults = {},
  MakeDefaultsOptional extends boolean = false,
  // 将嵌套的结构转为扁平化的
  PublicMixin = IntersectionMixin<Mixin> & IntersectionMixin<Extends>,
  // 提取 props
  PublicP = UnwrapMixinsType<PublicMixin, 'P'> & EnsureNonVoid<P>,
  // 提取 RawBindings,也就是 setup 返回的内容
  PublicB = UnwrapMixinsType<PublicMixin, 'B'> & EnsureNonVoid<B>,
  // 提取 data 返回的内容
  PublicD = UnwrapMixinsType<PublicMixin, 'D'> & EnsureNonVoid<D>,
  PublicC extends ComputedOptions = UnwrapMixinsType<PublicMixin, 'C'> &
    EnsureNonVoid<C>,
  PublicM extends MethodOptions = UnwrapMixinsType<PublicMixin, 'M'> &
    EnsureNonVoid<M>,
  PublicDefaults = UnwrapMixinsType<PublicMixin, 'Defaults'> &
    EnsureNonVoid<Defaults>
> = ComponentPublicInstance< // 上面结果传给 ComponentPublicInstance,生成 this context 中的内容
  PublicP,
  PublicB,
  PublicD,
  PublicC,
  PublicM,
  E,
  PublicProps,
  PublicDefaults,
  MakeDefaultsOptional,
  ComponentOptionsBase<P, B, D, C, M, Mixin, Extends, E, string, Defaults>
>
复制代码

以上就是总体大部分的 defineComponent 的实现,能够看出,他纯粹是为了类型推导而生的,同时,这里边用到了不少不少类型推导的技巧,还有一些这里没有涉及,感兴趣的同窗能够去仔细看下 Vue 中的实现。

相关文章
相关标签/搜索