作者:余温 | 来源:互联网 | 2023-10-11 20:24
迁移盲盒
当我们从Javascript一键转换Typescript的时候,any便是最省事的做法,对于维护并不友好(虽然能跑就行),同时每个变量对于我们来说都是盲盒,它到底是什么类型?
类型推导
基础类型的推导
基础数据类型的类型推导还是挺简单的
let a = 1;
type A = typeof a; // number;
let b = '2'
type B = typeof b; // string;
let c;
type C = typeof c; // undefined;
一个typeof就可以把原始值类型推导出来了!
我们整理下基础数据类型有哪些?
// 没错,7种数据类型
type Base = string | number | boolean | null | undefined | symbol | bigint;
对象的推导
这里我们来实现下普通对象如何推导
let obj = { a: 1, b: '2', c: true, d: null };
type Obj = typeof obj;
// { a: number; b: string; c: boolean; d: null; }
没错,也是这么简单!
数组的推导
为什么上面的对象除开数组呢?因为数组在typescript比较特殊,既可以用元祖来声明,又可以用数组来声明
也可以说数组包含了元祖
type isCOntain= [1, 2] extends Array ? true : false; // true
尝试继续通过typeof来实现推导
let arr = [1, '2', null, false];
type Arr = typeof arr; // (string | number | boolean | null)[] ???
type Arr1 = typeof arr[0] // string | number | boolean | null ???
好吧,得到的竟然是联合类型数组,是不是和我们预期的不一样?
我们定义一个数组,却没有声明类型,对于数组来说,默认就是 Array,由于不断填充了 number,string,null,boolean的值,最后变成了 Array,而且是每一个元素都是联合类型
重新整理下,我们需要的是啥?
[1, '2', null, false]
-> [number, string, null, boolean]
我们要的是元祖 [number, string, null, boolean]
,而不是数组 Array
整理下todo,我们需要的是:
let arr = [1, '2', null, false] as const;
type Arr = typeof arr; // readonly [1, '2', null, false]
type Arr1 = typeof arr[0] // 1
第一个 todo 实现了,第二个有点像,但又不对,我们要的是数据类型,而不是某个具体的值
实现一个转换类型的泛型
我们要的是 1 转 number, 'A' 转 string,只需要列出所有的基础类型做转换就可以了
type GetType = T extends string ? string :
T extends number ? number :
T extends boolean? boolean :
T extends null ? null :
T extends undefined ? undefined :
T extends symbol ? symbol :
T extends bigint ? bigint : T;
type Arr1 = typeof arr[0] // number
type Arr2 = typeof arr[1] // string
那再遍历一次元祖就可以实现整个数组的类型转换了
type TransArr,
R extends unknown[] = []> = {
'loop': TransArr [...R,
T extends Base ?
GetType: T[R['length']]
]
>,
'result': R,
}[T['length'] extends R['length'] ? 'result': 'loop'];
let arr = [1, '2', null, false] as const;
type Arr = typeof arr;
type ArrType = TransArr; // [number, string, null, boolean]
函数的推导
函数的推导其实没有必要,为什么这么说,函数参数和值类型不可控,除个别操作符或者明确类型
如 (a, b) => a * b ,返回值一定是number
如 (a) => Promise.resolve(a),返回值一定是Promise
let fn1 = (a, b) => a * b;
type Fn1 = typeof fn1; // (a: any, b: any) => number
function fn2(a) {
return Promise.resolve(a);
}
type Fn2 = typeof fn2; // (a: any) => Promise
大多是函数经过typeof后得到的结果是
(a: any, b: any, ...) => any;
这个类型可以限定参数数量更多的函数
function fn3(a, b, c) {
return a + b + c;
}
function fn4(d) {
return d + 1;
}
type Fn3 = typeof fn3; // (a: any, b: any, c: any) => any
type Fn4 = typeof fn4; // (d: any) => any
type F3_4 = Fn3 extends Fn4 ? true : false; // false
type F4_3 = Fn4 extends Fn3 ? true : false; // true
也就是说,参数多的函数总是包含了参数少的函数
根据上面的判断,我们可以通过这个来实现函数的判断
type isFunc = (() => any) extends T ? true : false;
完善推导
- 基础类型直接返回类型
- 数组用TransArr泛型转一次
- 函数直接返回typeof的值
- 遍历对象则用keyof实现
type Trans = T extends Base
? GetType : T extends Array
? TransArr : isFunc extends true
? T : {
[key in keyof T]: T[key] extends Base
? GetType : T[key] extends Array
? TransArr : Trans;
};
测试
let a1 = 1;
type test1 = Trans; // number
let a2 = '2';
type test2 = Trans; // string
let a3 = [1, '2', true, '3', 4] as const;
type test3 = TransArr;
// [number, string, boolean, string, number]
let a4 = {
a: 1,
b: true,
c: {
a: 1,
b: [1, '2']
},
d: [true, null]
} as const;
type test4 = Trans;
// {
// readonly a: number;
// readonly b: boolean;
// readonly c: {
// readonly a: number;
// readonly b: readonly [number, string];
// };
// readonly d: readonly [boolean, null];
// }
let a5 = {
a: [
{
b: [
{ c: 1 }
]
}
]
} as const;
type test5 = Trans;
// {
// readonly a: readonly [{
// readonly b: readonly [{
// readonly c: number;
// }];
// }];
// }
let a6 = (a, b, c) => a + b + c;
type test6 = Trans;
// (a: any, b: any, c: any) => any
let a7 = [
function fn() {
return 1;
},
(a, b) => a * b,
(a) => Promise.resolve(a)
] as const;
type test7 = TransArr;
// [() => number, (a: any, b: any) => number, (a: any) => Promise]
let a8 = {
a: 1,
b: [true, null],
c: [() => void, (a, b) => a],
d: {
e: [
(a, b) => null,
{
f: [1]
}
]
}
} as const;
type test8 = Trans;
// {
// readonly a: number;
// readonly b: readonly [boolean, null];
// readonly c: readonly [() => undefined, (a: any, b: any) => any];
// readonly d: {
// readonly e: readonly [(a: any, b: any) => null, {
// readonly f: readonly [number];
// }];
// };
// }