概述
一道TypeScript题目,快速上手TS(keyof,extends,typeof)
本人为ts初学者,本文题目和答案均来自于typescript高级用法之infer的理解与使用
由于原文并没有对语法部分进行详细说明,在理解后写下自己的步骤
建议阅读前先阅读下typescript高级用法之infer的理解与使用,有益于帮助理解
题目
interface Action<T> {
payload?: T;
type: string;
}
class EffectModule {
count = 1;
message = "hello!";
delay(input: Promise<number>) {
return input.then(i => ({
payload: `hello ${i}!`,
type: 'delay'
}));
}
setMessage(action: Action<Date>) {
return {
payload: action.payload!.getMilliseconds(),
type: "set-message"
};
}
}
// 修改 Connect 的类型,让 connected 的类型变成预期的类型
type Connect = (module: EffectModule) => any;
const connect: Connect = m => ({
delay: (input: number) => ({
type: 'delay',
payload: `hello 2`
}),
setMessage: (action: Date) => ({
type: "set-message",
payload: input.getMilliseconds()
})
});
//需要推断出的类型
type Connected = {
delay(input: number): Action<string>;
setMessage(action: Date): Action<number>;
};
export const connected: Connected = connect(new EffectModule());
题目所求的是 获取type Connect = (module: EffectModule) => any
,把any
变成预期的类型.
- 首先提取
EffectModule
的方法(注意:只是提取class里方法Function的键名)//首先展示如何获取function键名 type MethodName<T> = {[F in keyof T]:T[F] extends Function ? F :never}[keyof T] type EE = MethodName<EffectModule> // 此时EE的类型为 type EE = "delay" | "setMessage";
- 逐步解释,整体相当于
type MethodName<Obj> = Obj[keyof Obj]; //用Obj的键获取T的键值
- 再继续看
[F in keyof T]:T[F]
,这里是一个遍历语法//去掉其他代码单纯看这段 type MethodName只剩遍历版<T> = {[F in keyof T]:T[F]}; //1. [F in keyof T] 首先遍历T里的键,每一项为F //2. [F in keyof T]:T[F] 每一项F的值为T里去F键的键值 // 相当于 let obj = {a:1='a的键值',b:'b的键值'}; obj[a] = 1; //套入到EffectModule type EE = MethodName只剩遍历版<EffectModule>; //此时EE的类型为 type EE = { count: number; message: string; delay: (input: Promise<number>) => Promise<{ payload: string; type: string; }>; setMessage: (action: Action<Date>) => { payload: number; type: string; }; } //可见 "MethodName只剩遍历版" 只是把EffectModule的类型遍历了一次,和EffectModule本身相同
- 接上后面的三元表达式就开始实现过滤了
{[F in keyof T]:T[F] extends Function ? F :never}
// 由于我们已知{[F in keyof T]:T[F]}得到的类型EE为 type EE = { count: number; message: string; delay: (input: Promise<number>) => Promise<{ payload: string; type: string; }>; setMessage: (action: Action<Date>) => { payload: number; type: string; }; } //则{[F in keyof T]:T[F] extends Function ? F :never},在此时可以理解为 //{[F in keyof EE]:EE[F] extends Function ? F :never} //关键是EE[F] extends Function ? F :never,解释一下就是 EE里取键值F的类型 //EE[F]的类型是否继承于Function?是,就把EE[F]的类型赋值为F(也就是键名),否则就是never //为什么是never,后面在[keyof T]处再说明 //未完成版是因为有[keyof T]才完成过滤 type MethodName过滤未完成版<T> = {[F in keyof T]:T[F] extends Function ? F :never}; //套入EffectModule type EE = MethodName过滤未完成版<EffectModule>; //EE的类型为 type EE = { //此时,除去不是function的键名,其他键名和键值一一对应 count: never; message: never; delay: "delay"; setMessage: "setMessage"; }
- 接下来只剩下最后的
[keyof T]
了,就像第一点所说,这个"[ ]
"其实就是取前面对象的键值,类似Obj[key]
//因为此时已知 MethodName过滤未完成版<EffectModule>的值EE type EE = { //此时,除去不是function的键名,其他键名和键值一一对应 count: never; message: never; delay: "delay"; setMessage: "setMessage"; } //所以[keyof T]套入回EE,就相当于 type MethodName过滤完整版<EffectModule> = { count: never; message: never; delay: "delay"; setMessage: "setMessage"; }['delay'|'setMessage' /*keyof EffectModule的值*/]; //这里原本是[keyof T],此时T是代入EffectModule //所以得到结果 type EE = "delay" | "setMessage"; //这里有两个很关键的知识点 //1.keyof会返回数据类型的联合类型 //2.keyof在遇到值是never的时候不会返回 //到此,我们已经获取了这个class的两个function的键名
- 逐步解释,整体相当于
- 此时再回头看题目修改 Connect 的类型,让 connected 的类型变成预期的类型
- 首先看看connected的期望类型
type Connect = (module: EffectModule) => any; const connect: Connect = m => ({ delay: (input: number) => ({ type: 'delay', payload: `hello 2` }), setMessage: (action: Date) => ({ type: "set-message", payload: input.getMilliseconds() }) }); //可以很明显的看出Connect期望返回类型(仅方法部分) type Connected = { delay:(input: number) => Action<string>; setMessage:(action: Date) => Action<number>; } //而题目EffectModule类型(仅方法部分) type EffectModule = { delay:(input: Promise<T>) => Promise<Action<U>>; setMessage:(action: Action<T>) => Action<U> } //题目就是需要把Connect的返回值any推断成Connected //但实际我们只需要关注Connected里面两个函数 //我们可以直接写出EffectModule和Connected里函数泛型函数签名 //delay type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>>; type asyncMethodConnect<T, U> = (input: T) => Action<U>; //setMessage type syncMethod<T, U> = (action: Action<T>) => Action<U>; type syncMethodConnect<T, U> = (action: T) => Action<U>; //至于为什么是async,sync这种命名,因为题目里就是这么命名的
- 首先看看connected的期望类型
- 现在我们已经有了了
function
的键名,函数原类型,需要被推导类型的泛型,这里就可以利用keyof
的循环做一个分发,实现的关键是infer
type EffectMethodAssign<T> = T extends asyncMethod<infer U, infer V> ? asyncMethodConnect<U, V> : T extends syncMethod<infer U, infer V> ? syncMethodConnect<U, V> : never; //这里其实就是两个个很简单的三元运算符,关键是infer的用法,这里就不展开了 //这里已经就已经得到答案了 type Connected = (module: EffectModule) => { [F in MethodName<typeof module>]:EffectMethodAssign<typeof module[F]>; } //其实看到这里,我相信都是能看懂这段代码的 // [F in MethodName<typeof module>]负责取键值 // EffectMethodAssign<typeof module[F]> 负责分发类型
最后
以上就是如意猫咪为你收集整理的一道TypeScript题目,快速上手TS(keyof,extends,typeof)的全部内容,希望文章能够帮你解决一道TypeScript题目,快速上手TS(keyof,extends,typeof)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复