前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在Vue中给通过this.$refs引用的自定义控件添加类型声明

在Vue中给通过this.$refs引用的自定义控件添加类型声明

原创
作者头像
一介程序员
修改2022-02-28 18:19:32
2.8K0
修改2022-02-28 18:19:32
举报

0x00 hello world

最近在一个新项目中,尝试了vue2+typescript的组合,又又又碰到一个问题:定义了一个自定义控件Foo.vue,在控件中定义一个方法Bar(),使用自定义控件的时候,添加ref='foo'并且希望通过使用this.$refs.foo.Bar()调用方法,当然是可以成功调用的,但是在TypeScript中,他会报错。

图一
图一

后来我折腾了好久,想出了一个不是那么优雅的方法:

图2
图2

这个样子,虽然不报错了,但是生生的把TypeScript写成了AnyScript,如果我修改了Bar的定义,比如添加了一个参数,这边就不会提示错误,告诉我缺一个参数,就失去了使用TypeScript的意义。

0x01 优雅的方式

我试过(this.$refs.foo as Foo).Bar();不行,试过(this.$refs.foo as typeof Foo).Bar();不行,鬼使神差的,我试了下(this.$refs.foo as InstanceType<typeof Foo>).Bar();,居然可以!就觉得很神奇!这什么意思呀。

图3
图3

0x02 原理?

为了搞明白这到底是什么意思,我研究了一下vue的类型定义文件

Vue.extend的定义如下:

  extend<Data, Methods, Computed, PropNames extends string = never>(options?: ThisTypedComponentOptionsWithArrayProps<V, Data, Methods, Computed, PropNames>): ExtendedVue<V, Data, Methods, Computed, Record<PropNames, any>>;
  extend<Data, Methods, Computed, Props>(options?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props>): ExtendedVue<V, Data, Methods, Computed, Props>;
  extend<PropNames extends string = never>(definition: FunctionalComponentOptions<Record<PropNames, any>, PropNames[]>): ExtendedVue<V, {}, {}, {}, Record<PropNames, any>>;
  extend<Props>(definition: FunctionalComponentOptions<Props, RecordPropsDefinition<Props>>): ExtendedVue<V, {}, {}, {}, Props>;
  extend(options?: ComponentOptions<V>): ExtendedVue<V, {}, {}, {}, {}>;

返回的是一个ExtendedVue,他的定义如下:

export type ExtendedVue<Instance extends Vue, Data, Methods, Computed, Props> = VueConstructor<CombinedVueInstance<Instance, Data, Methods, Computed, Props> & Vue>;

返回的是一个VueConstructor,他的定义如下:

export interface VueConstructor<V extends Vue = Vue> {
  new <Data = object, Methods = object, Computed = object, PropNames extends string = never>(options?: ThisTypedComponentOptionsWithArrayProps<V, Data, Methods, Computed, PropNames>): CombinedVueInstance<V, Data, Methods, Computed, Record<PropNames, any>>;
  // ideally, the return type should just contain Props, not Record<keyof Props, any>. But TS requires to have Base constructors with the same return type.
  new <Data = object, Methods = object, Computed = object, Props = object>(options?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props>): CombinedVueInstance<V, Data, Methods, Computed, Record<keyof Props, any>>;
  new (options?: ComponentOptions<V>): CombinedVueInstance<V, object, object, object, Record<keyof object, any>>;

  extend<Data, Methods, Computed, PropNames extends string = never>(options?: ThisTypedComponentOptionsWithArrayProps<V, Data, Methods, Computed, PropNames>): ExtendedVue<V, Data, Methods, Computed, Record<PropNames, any>>;
  extend<Data, Methods, Computed, Props>(options?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props>): ExtendedVue<V, Data, Methods, Computed, Props>;
  extend<PropNames extends string = never>(definition: FunctionalComponentOptions<Record<PropNames, any>, PropNames[]>): ExtendedVue<V, {}, {}, {}, Record<PropNames, any>>;
  extend<Props>(definition: FunctionalComponentOptions<Props, RecordPropsDefinition<Props>>): ExtendedVue<V, {}, {}, {}, Props>;
  extend(options?: ComponentOptions<V>): ExtendedVue<V, {}, {}, {}, {}>;

  nextTick<T>(callback: (this: T) => void, context?: T): void;
  nextTick(): Promise<void>
  set<T>(object: object, key: string | number, value: T): T;
  set<T>(array: T[], key: number, value: T): T;
  delete(object: object, key: string | number): void;
  delete<T>(array: T[], key: number): void;

  directive(
    id: string,
    definition?: DirectiveOptions | DirectiveFunction
  ): DirectiveOptions;
  filter(id: string, definition?: Function): Function;

  component(id: string): VueConstructor;
  component<VC extends VueConstructor>(id: string, constructor: VC): VC;
  component<Data, Methods, Computed, Props>(id: string, definition: AsyncComponent<Data, Methods, Computed, Props>): ExtendedVue<V, Data, Methods, Computed, Props>;
  component<Data, Methods, Computed, PropNames extends string = never>(id: string, definition?: ThisTypedComponentOptionsWithArrayProps<V, Data, Methods, Computed, PropNames>): ExtendedVue<V, Data, Methods, Computed, Record<PropNames, any>>;
  component<Data, Methods, Computed, Props>(id: string, definition?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props>): ExtendedVue<V, Data, Methods, Computed, Props>;
  component<PropNames extends string>(id: string, definition: FunctionalComponentOptions<Record<PropNames, any>, PropNames[]>): ExtendedVue<V, {}, {}, {}, Record<PropNames, any>>;
  component<Props>(id: string, definition: FunctionalComponentOptions<Props, RecordPropsDefinition<Props>>): ExtendedVue<V, {}, {}, {}, Props>;
  component(id: string, definition?: ComponentOptions<V>): ExtendedVue<V, {}, {}, {}, {}>;

  use<T>(plugin: PluginObject<T> | PluginFunction<T>, options?: T): VueConstructor<V>;
  use(plugin: PluginObject<any> | PluginFunction<any>, ...options: any[]): VueConstructor<V>;
  mixin(mixin: VueConstructor | ComponentOptions<Vue>): VueConstructor<V>;
  compile(template: string): {
    render(createElement: typeof Vue.prototype.$createElement): VNode;
    staticRenderFns: (() => VNode)[];
  };

  observable<T>(obj: T): T;

  util: {
    warn(msg: string, vm?: InstanceType<VueConstructor>): void;
  };

  config: VueConfiguration;
  version: string;
}

注意前面3行,那几个名字为new的函数:

  new <Data = object, Methods = object, Computed = object, PropNames extends string = never>(options?: ThisTypedComponentOptionsWithArrayProps<V, Data, Methods, Computed, PropNames>): CombinedVueInstance<V, Data, Methods, Computed, Record<PropNames, any>>;
  // ideally, the return type should just contain Props, not Record<keyof Props, any>. But TS requires to have Base constructors with the same return type.
  new <Data = object, Methods = object, Computed = object, Props = object>(options?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props>): CombinedVueInstance<V, Data, Methods, Computed, Record<keyof Props, any>>;
  new (options?: ComponentOptions<V>): CombinedVueInstance<V, object, object, object, Record<keyof object, any>>;

也就是说new一个Vue实例的时候,返回的类型是CombinedVueInstance,这个类型的定义如下:

export type CombinedVueInstance<Instance extends Vue, Data, Methods, Computed, Props> =  Data & Methods & Computed & Props & Instance;

这个 CombinedVueInstance 的用处就是把组件定义的内容和Vue本身的内容组合到一起。

0x03 总结

总结下来就是:

  1. 在JavaScript中,一个东西(函数?类型?)的类型有两种,一种是他本来的类型,一种是实例化之后的实例类型,这两个类型有可能是不一样的;
  2. Vue的类型和Vue实例化的后的类型不是同一个类型,Vue的类型是VueConstructor类型,实例化后的类型是CombinedVueInstance
  3. 我需要的是一个实例化之后的类型,所以Foo是我导入的一个变量,通过type of Foo取得它的类型,但是,但是我需要的是它实例化后的类型,所以还需要通过InstanceType<typeof Foo>获取。

0x04 特别感谢

感谢TDP成员若海 在这个过程中给我的无私帮助!

腾云先锋(TDP,Tencent Cloud Developer Pioneer)是腾讯云GTS官方组建并运营的技术开发者群体。这里有最专业的开发者&客户,能与产品人员亲密接触,专有的问题&需求反馈渠道,有一群志同道合的兄弟姐妹。

有兴趣的朋友可以关注 腾云先锋团队 加入TDP。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x00 hello world
  • 0x01 优雅的方式
  • 0x02 原理?
  • 0x03 总结
  • 0x04 特别感谢
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档