Better

Ethan的博客,欢迎访问交流

this in typescript

之前在书写类型时,想要参考下官方提供的声明文件是如何设计的,由此发现声明文件中的 this 关键字,之前的确没有接触过这一块,详细了解一哈!

this 参数

这也是启发我去了解 this 含义的来源。这种情况下的目的主要是:限制调用函数时的 this 类型

我们了解到 this 的指向在 js 中是比较神奇的,大致有如下几种常见情况

  • 作为对象的方法调用
  • 作为普通函数调用
  • 作为构造器调用
  • 作为 Function.prototype.call 和 Function.prototype.bind 调用

作为对象方法使用是,TS 能够自动推到出确的类型,但作为普通函数使用时,情况就有些不一样了,比如如下代码

const obj = {
  name: "yj",
  getName() {
    return this.name
  },
}
const fn1 = obj.getName
fn1()

上述代码中,TS 编译器不会检查出错误,但运行时会报错,这时候我们就需要借助 this 类型声明来完善类型如下

interface Obj {
  name: string
  getName(this: Obj): string
}
const obj: Obj = {
  name: "yj",
  getName() {
    return this.name
  },
}
obj.getName()
const fn1 = obj.getName
fn1()

这样 TS 就会正确的推导出类型并检测出错误。以上是 this 中比较好理解的一部分。

接下来了解作为构造器调用时存在的问题(其实这个文件已经不尖锐了,毕竟我们现在都用 class 关键字),比如一下代码会报检测错误

function People(name: string) {
  this.name = name // check error
}
People.prototype.getName = function() {
  return this.name
}
const people = new People() // check error

可上面的写法实际并不存在问题呀,如果要解决的话,就需要借助 this 来显示声明 Constructor 类型。下面是例子

interface People {
  name: string
  getName(): string
}
interface PeopleConstructor {
  new (name: string): People // 声明可以作为构造函数调用
  prototype: People // 声明 prototype,支持后续修改 prototype
}
const ctor = (function(this: People, name: string) {
  this.name = name
} as unknown) as PeopleConstructor // 类型不兼容,二次转型

ctor.prototype.getName = function() {
  return this.name
}

const people = new ctor("this")

至于通过 call 或 apply 改变 this 的方式,Typescript 借助 this 参数也支持对 call 调用的类型检查。这里有意思问题是,call 函数是如何做到的呢。

第一步:获取函数中 this 的类型。typescript 已经内置好 ThisParameterType 工具供我们使用

interface People {
  name: string
}
function ctor(this: People) {}

type ThisArg = ThisParameterType<typeof ctor>

关于 ThisParameterType 的实现也比较简单,我们可以通过 infer type 实现,比如我们实现一个 ThisParameterType2 如下

type ThisParameterType2<
T extends (this: any, ...args: any[]) => any
> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;

这里我们模拟一个 call2 的函数声明,首先利用 typescript 类型声明合并的特性,为内置提供的 CallableFunction 声明扩展一个 call2 函数如下。

interface CallableFunction {
  call2<T>(this: (this: T) => any, thisArg: T): any
}

注意上述的声明只是支持对于传入的 context 进行类型检查,但 call 后续还是可以传入其他参数的,需要进一步的添加返回值和其余参数类型如下

interface CallableFunction {
  call2<T, A extends any[], R>(
    this: (this: T, ...args: A) => R,
    thisArg: T,
    ...args: A
  ): R
}

ThisType

typescript 提供的这个工具也很有意思,可以看下官方文档对这个的解释:ThisType

This utility does not return a transformed type. Instead, it serves as a marker for a contextual this type. Note that the noImplicitThis flag must be enabled to use this utility.

很有趣,感觉就是给 Vue 提供的一样,参考的博客文章中也有提到 Vue2.5 通过 ThisType 对 vue 的 typescript 支持进行了一波增强。

下面是官网抄来的示例

type ObjectDescriptor<D, M> = {
  data?: D;
  methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M
};

function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
  let data: object = desc.data || {};
  let methods: object = desc.methods || {};
  return { ...data, ...methods } as D & M;
}

let obj = makeObject({
  data: { x: 0, y: 0 },
  methods: {
    moveBy(dx: number, dy: number) {
      this.x += dx; // Strongly typed this
      this.y += dy; // Strongly typed this
    },
  },
});

obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);

参考



留言