【Typescript】为函数事件做类型标注

129 阅读1分钟

1.问题引入

考虑一个watch函数

const personWatcher = watch({
    firstName: 'Saoirse',
    lastName: 'Ronan',
    age: 26,
    sex: '男'
})

personWatcher.on('firstNameChanged', (oldVal, newVal)=>{})

若要对这样一个Watche函数进行类型标注,简单的标注方式即为如下代码:

type Watcher = {
    on(eventName: string,
    callback:( oldvalue:any, newValue: any) => void):void
}

declare function watch(obj: object): Watcher;

这样标注有很大的缺陷就是在填写eventName参数时没有智能提示以及类型约束,写了ts相当于白写

2.eventName的类型标注

所以需要使用模板字符串增强类型标注

type Watcher = {
on( eventName:`${'firstName'|'lastName'|'age'|'sex'}Changed`,
    //...
)
}

如此一来eventName就能有类型约束了。进一步的,可以将模板字符串内的值使用动态的键值改写,即可用泛型解决上文代码没有动态抽取属性名的问题。

type Watcher:<T> = {
on( eventName:`${keyof T}Changed`,
    //...
)
}
declare function watch<T>(obj: T):Watcher<T>

实际上,代码仍然有一段缺陷,因为泛型T有可能是Symbol类型,所以keyof Symbol会出现报错,解决方法即为提取泛型中的String类型即可,如下代码所示:

type Watcher:<T> = {
on( eventName:`${string & keyof T}Changed`,
    //...
)
}

3.callback的类型标注

解决完eventName的标注,再完善callback参数类型的标注。callback函数的参数应当为某个字段的类型,所以标注代码如下所示。

首先在进行callback标注时,应当是在调用on的时候才能确定具体属性被监听,而不是在Watcher被调用时确定具体属性被监听,也即on方法中需要一个泛型K接收这个属性。如此一来便能对callback进行标注

type Watcher<T> = {
    on<K extends keyof T>(eventName: string,
    callback:( oldvalue:T[k], newValue: any) => void):void
}

但目前位置代码会有一个BUG,比如以下代码能出现callback参数的类型提示,但是明显不是我们需要的效果。(eventName与泛型K监听的属性不一致)。

type Watcher<T> = {
    on<K extends keyof T>(eventName: `${string & keyof T}Changed`,
    callback:( oldvalue:T[k], newValue: any) => void):void
}
personWatcher.on<'age'>('sexChanged', (olbValue...))

所以在泛型中需要加上限定,参考以下代码:

type Watcher<T> = {
    on<K extends keyof T>(eventName: `${K}Changed`,
    callback:( oldvalue:T[k], newValue: any) => void):void
}
personWatcher.on<'age'>('sexChanged', (olbValue...))

如此一来ts可以自动推导K的类型,而不必在on方法中填写泛型K。且实现了callback的类型标注