this ts 方法获取_一个TypeScript简例,以及Vue支持TS的一些些事儿

本文介绍了如何在Vue组件中利用TypeScript的ThisType工具类型解决options对象中函数的this类型问题。通过创建一个create工具函数,结合Contextual Typing,实现了对props、data和methods的正确类型推导,确保在组件中可以正确访问所需字段和方法。
摘要由CSDN通过智能技术生成

前言

我们知道在Vue里用options来声明一个组件,举一个简单的例子

const options = {
  props: {
    name: {
      type: String,
    },
  },
  data() {
    return {
      score: 100
    }
  },
  methods: {
    click() {
      this.score++
    },
    say() {
      alert(`${this.name}: ${this.score}`)
    }
  },
}
export default options

这样在JavaScript里固然是没什么问题,但是在TS里就很郁闷了,因为options是一个plain object,涉及到函数的时候this的类型就很成问题。在上面的例子里,data()的this是一个字面量

{
  props: {
    name: {
      type: StringConstructor
    }
  }
  data(): {
    score: number
  }
  methods: {
    click(): void
    say(): void
  }
}

而click()的类型也是一个字面量

{
  click(): void
  say(): void
}

这样的类型虽然“很对”,但拿来是完全没有任何卵用的,而且click和say两个方法根本编译不过,因为他们的this上没有预期的name和score这些字段。

而预期的结果中,我们应该

1、在methods里能访问props里声明的字段、data()所返回的字段和methods里所声明的方法,这里的this就是

{
  name: string
  score: number
  click(): void
  say(): void
}

2、在data()里能访问props里声明的字段,这里的this就是

{
  name: string
}

这篇文章就以上面的这组options为例子,来解释一下如何通过Contextual Typing和ThisType<T>工具类型获得正确的options类型声明。

正戏

这里的阶段性目标是要让options当中的这些函数声明具有“校正”过的this类型,需要用到TS内置的一个工具类型叫做ThisType<T>,官方的例子应该很好看懂。对着官方例子,这里先给methods对象打上预期的ThisType,那么Options就是

interface Options<Data, Props, Methods> {
  props?: Props,
  data?: Data
  methods?: Methods & ThisType<Data & Props & Methods>
}

然后通过一个create工具函数来“诱导”它的类型

function create<Data, Props, Methods>(options: Options<Data, Props, Methods>) {
  return options
}

注意这里的create函数什么也不干,直接return options,因为它没有任何实际值运算,而是为了利用TS的另一个特性叫做Contextual Typing来自动推导出Data, Props, Methods三个泛型参数的实际类型。

这时候代码可以写成

const options = create({
  props: {
    name: 'jim',
  },
  data: {
    score: 100,
  },
  methods: {
    click() {
      this.score++
    },
    say() {
      return `${this.name}: ${this.score}`
    },
  },
})

这个代码已经可以经过类型检验了,而剩下的过程则是一步步的把data和props变成预期的样子。

首先重新声明props的类型,成那个预期的嵌套描述的样子

interface Options<Data, Props, Methods> {
  props?: {
    [propName in keyof Props]: {
      type: () => Props[propName]
    }
  }
  data?: Data
  methods?: Methods & ThisType<Data & Props & Methods>
}

这里的Props预期是一个plain object类型,在上面的例子里它的实际类型应该是

{
  name: string
}

这时候类型已经对了,为了简化声明,可以抽取泛型

type PropType<T> = { (): T }

interface PropOption<T> {
  type: PropType<T>
}

type PropsDefinition<T> = {
  [propName in keyof T]: PropOption<T[propName]>
}

interface Options<Data, Props, Methods> {
  props?: PropsDefinition<Props>
  data?: Data
  methods?: Methods & ThisType<Data & Props & Methods>
}

接下来是data的类型,先不考虑this,这里其实很简单,有两种办法。

第一种比较少动脑筋,可以说是头痛医头脚痛医脚,修改一下Options的声明

interface Options<DataInitializer extends () => {}, Props, Methods> {
  data?: DataInitializer
  props?: PropsDefinition<Props>
  methods?: Methods & ThisType<ReturnType<DataInitializer> & Props & Methods>
}

data的类型是一个DataInitializer,再利用ReturnType<T>工具类型来用它的返回值类型表示Data类型。对应地,create的类型声明也要跟着改下

function create<DataInitializer extends () => {}, Props, Methods>(options: Options<DataInitializer, Props, Methods>) {
  return
}

这样不怎么需要动脑筋,但是异常繁琐,既然已经有了上面推演props类型的经验,岂不是应该可以把它写成这样

interface Options<Data, Props, Methods> {
  props?: PropsDefinition<Props>
  data?: () => Data
  methods?: Methods & ThisType<Data & Props & Methods>
}

function create<Data, Props, Methods>(options: Options<Data, Props, Methods>) {
  return
}

很好,然后再限定它只能访问props里那些字段,手工显式指定就行

interface Options<Data, Props, Methods> {
  props?: PropsDefinition<Props>
  data?: (this: Props) => Data
  methods?: Methods & ThisType<Data & Props & Methods>
}

稍微提取泛型重构一下

type DataDefinition<Data, Props> = (this: Props) => Data

interface Options<Data, Props, Methods> {
  props?: PropsDefinition<Props>
  data?: DataDefinition<Data, Props>
  methods?: Methods & ThisType<Data & Props & Methods>
}

最终就可以愚快地使用create工具函数来对options进行带泛型推导的Contexual Typing了

const options = create({
  props: {
    name: {
      type: String,
    },
  },
  data() {
    // this
    return {
      score: 100,
    }
  },
  methods: {
    click() {
      this.score++
    },
    say() {
      // this
      return `${this.name}: ${this.score}`
    },
  },
})
export default options

可以放进playground或者vs code里尝试下,利用鼠标悬浮智能提示,可以看到不论是options还是data和methods里的this类型都能正确的推导。

以上代码就是Vue.extend的类型声明的一个极简版,阐述了它的基本思路,完整的Vue.extend代码大家有兴趣可以去vue源代码里找,主要在types/options.d.ts里面。

那么这个有啥用呢,最直观的一点是,如果你的代码里需要创建options,但又不方便用Vue.extend,而且是想创建一个“干净”的options——比方说,一个mixin——那么可以自己把Vue.extend的类型声明copy一份出来,然后像上面一样写一个“类型诱导”函数,直接return options。事实上@vue/composition-api就有类似的做法

46d291e5cc7f999120d9647a8254e4f2.png

这里的createComponent就是一个只为了获得Contexual Typing而写的透明的工具函数,因为截图里隐去了上面的大量类型声明,所以单独看这个return的话难免怀疑这是在搞笑。

讨好TypeScript编译器,可不比讨好女生容易啊……
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值