希望实现这样一个类型:
对数组做分组,比如 1、2、3、4、5 的数组,每两个为 1 组,那就可以分为 1、2 和 3、4 以及 5 这三个 Chunk。
type arr = [1, 2, 3, 4, 5];
type Chunk<s
Arr extends any[],
N extends number,
Current extends any[] = [],
Res extends any[] = []
> = Arr extends [infer First, ...infer Rest]
? Current["length"] extends N
? Chunk<Rest, N, [First], [...Res, Current]>
: Chunk<Rest, N, [...Current, First], Res>
: [...Res, Current];
type res = Chunk<arr, 2>;
我们希望实现这样一个功能:
根据数组类型,比如 [‘a’, ‘b’, ‘c’] 的元组类型,再加上值的类型 'xxx',构造出这样的索引类型:
{
a: {
b: {
c: 'xxx'
}
}
}
这个依然是提取、构造、递归,只不过是对数组类型做提取,构造索引类型,然后递归的这样一层层处理。
type TupleToNestedObject<T extends unknown[], V> = T extends [
infer First,
...infer Rest
]
? {
[K in First & (string | number | symbol)]: Rest extends any[]
? TupleToNestedObject<Rest, V>
: V;
}
: V;
type res = TupleToNestedObject<["a", "b", 2, 4, "beyond"], 6>;
// type res = {
// a: {
// b: {
// 2: {
// 4: {
// beyond: 6;
// };
// };
// };
// };
// };
注意K in First无需keyof,因为First本身就是一个具体的类型了。其次是作为索引,需要满足是 string | number | symbol 类型
把一个索引类型的某些 Key 转为 可选的,其余的 Key 不变
type Copy<Obj extends Record<string, any>> = {
[Key in keyof Obj]: Obj[Key];
};
type PartialObjectPropByKeys<
R extends Record<string, any>,
U extends keyof P
> = Copy<Partial<Pick<P, U>> & Omit<R, U>>;
type P = {
name: string;
age: number;
hobby: any[];
};
type res = PartialObjectPropByKeys<P, "hobby" | "age">;
// type res = {
// hobby?: any[] | undefined;
// age?: number | undefined;
// name: string;
// }
这里的Copy是为了构造映射类型让ts做类型推断,否则推出来的值是未经计算的交叉类型,编译器里不能直接显示结果。但实际功能上,加和不加Copy都是一样的。
declare function func(name: string): string;
declare function func(name: number): number;
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any) {
return a + b;
}
interface Fun {
(name: string): string;
(name: number): number;
}
declare const fun: Fun;
type Fun = (name: string) => string & ((name: number) => number);
declare const fun: Fun;
如果函数是重载函数,TS的内置类型ReturnType只会返回最后一次重载时返回的类型
'a' | 'b' | 'c'
转成 ['a', 'b', 'c']
已知:
- 重载函数能通过函数交叉的方式声明
- 能实现联合转交叉 那么:就能拿到联合类型的最后一个类型 以下Type在体操基础有说明,利用函数参数逆变,来获取交叉类型
type UnionToIntersection<U> =
(U extends U ? (x: U) => unknown : never) extends (x: infer R) => unknown
? R
: never
type UnionToFuncIntersection<T> = UnionToIntersection<T extends any ? () => T : never>;
UnionToFuncIntersection作用:
我们对联合类型 T 做下处理,用 T extends any 触发分布式条件类型的特性,它会把联合类型的每个类型单独传入做计算,最后把计算结果合并成联合类型。把每个类型构造成一个函数类型传入。
这样,返回的交叉类型也就达到了函数重载的目的:
type res = UnionToFuncIntersection<"beyond" | "typescript">;
// type res = (() => "beyond") & (() => "typescript")
那么就可以使用ReturnType取最后一个类型,再利用Exclude把最后一个类型从联合类型中去掉,参与递归,构成元组
type UnionToTuple<U> = UnionToFuncIntersection<U> extends () => infer RT
? [...UnionToTuple<Exclude<U, RT>>, RT]
: [];
type res = UnionToTuple<"beyond" | "typescript" | "2023">;
// type res = ["beyond", "typescript", "2023"]
完整代码
type UnionToIntersection<U> = (
U extends U ? (x: U) => unknown : never
) extends (x: infer R) => unknown
? R
: never;
type UnionToFuncIntersection<T> = UnionToIntersection<
T extends any ? () => T : never
>;
type UnionToTuple<U> = UnionToFuncIntersection<U> extends () => infer RT
? [...UnionToTuple<Exclude<U, RT>>, RT]
: [];
type res = UnionToTuple<"beyond" | "typescript" | "2023">;
// type res = ["beyond", "typescript", "2023"]
有这样一个 curring 函数,接受一个函数,返回柯里化后的函数。
// 传入
const func = (a: string, b: number, c: boolean) => {};
// 返回
(a: string) => (b: number) => (c: boolean) => void
declare function currying(fn: xxx): xxx;
明显,这里返回值类型和参数类型是有关系的,所以要用类型编程。
传入的是函数类型,可以用模式匹配提取参数和返回值的类型来,构造成新的函数类型返回。
每有一个参数就返回一层函数,具体层数是不确定的,所以要用递归。
type CurriedFunc<Params, Return> = Params extends [infer Arg, ...infer Rest]
? (arg: Arg) => CurriedFunc<Rest, Return>
: never;
declare function currying<Func>(
fn: Func
): Func extends (...args: infer Params) => infer Result
? CurriedFunc<Params, Result>
: never;
const res = currying((a: string, b: boolean, c: Date) => {});
// const res: (arg: string) => (arg: boolean) => (arg: Date) => never
const res = join('-')('beyond', 'and', 'typescript');
有这样一个 join 函数,它是一个高阶函数,第一次调用传入分隔符,第二次传入多个字符串,然后返回它们 join 之后的结果。
定义函数
declare function join<Delimiter extends string>(
delimiter: Delimiter
): <Items extends string[]>(...parts: Items) => JoinType<Items, Delimiter>;
实现JoinType
type JoinType<
Items extends any[],
Delimiter extends string,
Res extends string = ""
> = Items extends [infer First, ...infer Rest]
? JoinType<
Rest,
Delimiter,
Res extends ""
? `${First & string}`
: `${Res}${Delimiter}${First & string}`
>
: Res;
type res = JoinType<["beyond", "typescript", "2023"], "-">;
// type res = "beyond-typescript-2023"
这样就实现啦,完整代码
type JoinType<
Items extends any[],
Delimiter extends string,
Res extends string = ""
> = Items extends [infer First, ...infer Rest]
? JoinType<
Rest,
Delimiter,
Res extends ""
? `${First & string}`
: `${Res}${Delimiter}${First & string}`
>
: Res;
type res = JoinType<["beyond", "typescript", "2023"], "-">;
// type res = "beyond-typescript-2023"
declare function join<Delimiter extends string>(
delimiter: Delimiter
): <Items extends string[]>(...parts: Items) => JoinType<Items, Delimiter>;
const res2 = join("&")("beyond", "study", "typescript");
// const res2: "beyond&study&typescript"
比如这样一个索引类型:
type obj = {
aaa_bbb: string;
bbb_ccc: [
{
ccc_ddd: string;
},
{
ddd_eee: string;
eee_fff: {
fff_ggg: string;
}
}
]
}
要求转成这样:
type DeepCamelizeRes = {
aaaBbb: string;
bbbCcc: [{
cccDdd: string;
}, {
dddEee: string;
eeeFff: {
fffGgg: string;
};
}];
}
直接看代码
type DeepCamelize<Obj extends Record<string, any>> = Obj extends any[]
? CamelizeArr<Obj>
: {
[K in keyof Obj as K extends `${infer First}_${infer Rest}`
? `${First}${Capitalize<Rest>}`
: K]: DeepCamelize<Obj[K]>;
};
type CamelizeArr<Arr extends any[]> = Arr extends [infer First, ...infer Rest]
? [
DeepCamelize<First extends Record<string, any> ? First : never>,
...CamelizeArr<Rest>
]
: [];
type res = DeepCamelize<{
aaa_bbb: string;
bbb_ccc: [
{
ccc_ddd: string;
},
{
ddd_eee: string;
eee_fff: {
fff_ggg: string;
};
}
];
}>;
// type res = {
// aaaBbb: string;
// bbbCcc: [{
// cccDdd: string;
// }, {
// dddEee: string;
// eeeFff: {
// fffGgg: string;
// };
// }];
// }
type Obj = {
a: {
b: {
b1: string
b2: string
}
c: {
c1: string;
c2: string;
}
},
}
希望返回 a、a.b、a.b.b1、a.b.b2、a.c、a.c.c1、a.c.c2 这些全部的 path。
type Obj = {
a: {
b: {
b1: string;
b2: string;
};
c: {
c1: string;
c2: string;
};
};
};
type AllKeyPath<Obj extends Record<string, any>> = {
[Key in keyof Obj]: Key extends string
? Obj[Key] extends Record<string, any>
? Key | `${Key}.${AllKeyPath<Obj[Key]>}`
: Key
: never;
}[keyof Obj];
type res = AllKeyPath<Obj>;
// type res = "a" | "a.b" | "a.c" | "a.b.b1" | "a.b.b2" | "a.c.c1" | "a.c.c2"
参数 Obj
是待处理的索引类型,通过 Record<string, any>
约束。
用映射类型的语法,遍历Key
,并在 value
部分根据每个 Key
去构造以它为开头的 path
因为推导出来的 Key
默认是 unknown
,而其实明显是个 string
,所以 Key extends string
判断一下,后面的分支里 Key
就都是 string
了。
如果 Obj[Key]
依然是个索引类型的话,就递归构造,否则,返回当前的 Key
。
我们最终需要的是 value
部分,所以取 [keyof Obj]
的值。keyof Obj
是 key
的联合类型,那么传入之后得到的就是所有 key
对应的 value
的联合类型。
这样就完成了所有 path
的递归生成
实现这样一个高级类型,对 A、B 两个索引类型做合并,如果是只有 A 中有的不变,如果是 A、B 都有的就变为可选,只有 B 中有的也变为可选。
type A = {
a: 1;
b: 2;
c: 3;
};
type B = {
b: 4;
c: 5;
d: 6;
};
type Defaultize<A, B> = Pick<A, Exclude<keyof A, keyof B>> &
Partial<Pick<A, Extract<keyof A, keyof B>>> &
Partial<Pick<B, Exclude<keyof B, keyof A>>>;
type Copy<T> = {
[K in keyof T]: T[K];
};
type res = Copy<Defaultize<A, B>>;
// type res = {
// a: 1;
// b?: 2 | undefined;
// c?: 3 | undefined;
// d?: 6 | undefined;
// }
type Demo<T extends string[]> = T extends [infer F, ...infer R] ? `${F}` : T;
// 不能将类型“F”分配给类型“string | number | bigint | boolean | null | undefined”。
type Demo2<T extends string[]> = T extends [infer F, ...infer R]
? `${F extends string ? F : never}`
: T;
type Demo3<T extends string[]> = T extends [infer F, ...infer R]
? `${F & string}`
: T;
type Demo4<T extends string[]> = T extends [infer F extends string, ...infer R]
? `${F}`
: T;
用于推导类型时的断言,会进行类型转换。