之前在书写类型时,想要参考下官方提供的声明文件是如何设计的,由此发现声明文件中的 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);