#前言
写了一段时间的TS了,正好看到一篇文章,来自**掘金阿宝哥(opens new window)**,里面出了30道TS的练习题,我尝试了一下解题。通过练习发现自己对TS的协变、泛型等加深了理解,不过还是没能自己解答所有问题😂
#第1道
原因:因为T内的内容多余User
解决措施:
type User = {
id: number;
kind: string;
};
function makeCustomer<T extends User>(u: T): T {
return {
...u,
id: u.id,
kind: 'customer'
};
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
#第2道-函数重载
function f(a: string, b: string)
function f(a: number, b: number)
function f(a: string | number, b: string | number) {
if (typeof a === 'string') {
return a + ':' + b;
} else {
return a + b;
}
}
f(2, 3);
f(1, 'a');
f('a', 2);
f('a', 'b');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
#第3道
type Foo = {
a?: number;
b: string;
c?: boolean;
}
type SetRequired<T, keys extends keyof T> = Required<Pick<T, keys>> & Omit<T, keys>;
// 测试用例
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
#第4道
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
type ValueOf<T> = T[keyof T];
type ConditionalPick<T, U> = Pick<T, NonNullable<ValueOf<{ [key in keyof T]: T[key] extends U ? key : null; }>>>;
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, string>;
//=> {a: string}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
#第5道
type Fn = (a: number, b: string) => number
type AppendArgument<F extends (...arg: any) => any, A> = (x: A, ...rest: Parameters<F>) => ReturnType<F>;
type FinalFn = AppendArgument<Fn, boolean>;
// (x: boolean, a: number, b: string) => number
1
2
3
4
5
2
3
4
5
#第6道
type NaiveFlat<T extends any[]> = T[number] extends any[] ? NaiveFlat<T[number]> : T[number];
// 测试用例:
type NaiveResult = NaiveFlat<[['a'], ['b', 'c'], ['d']]>;
type DeepFlat<T extends any[]> = {
[key in keyof T]: T[key] extends any[] ? NaiveFlat<T[key]> : T[key];
}[number];
// 测试用例
type Deep = [['a'], ['b', 'c'], [['d']], [[[['e']]]]];
type DeepTestResult = DeepFlat<Deep>;
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
#第7道
type EmptyObject = {
[key in string]: never
};
// 测试用例
const shouldPass: EmptyObject = {}; // 可以正常赋值
const shouldFail: EmptyObject = { // 将出现编译错误
prop: "TS"
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
// 来自:https://dev.to/macsikora/advanced-typescript-exercises-answer-7-3k5e
type SomeType = {
prop: string
}
// 更改以下函数的类型定义,让它的参数只允许严格SomeType类型的值
function takeSomeTypeOnly<T1 extends SomeType, T2 = T1>(x: {
[key in keyof T2]: key extends keyof T1 ? T2[key]: never
} ) { return x }
// 测试用例:
const x = { prop: 'a' };
takeSomeTypeOnly(x) // 可以正常调用
const y = { prop: 'a', addditionalProp: 'x' };
takeSomeTypeOnly(y) // 将出现编译错误
// 2
type Exclusive<T, T2 extends T> = {
[key in keyof T2]: key extends keyof T ? T2[key] : never;
}
// 更改以下函数的类型定义,让它的参数只允许严格SomeType类型的值
function takeSomeTypeOnly<T extends SomeType>(x: Exclusive<SomeType, T>) { return x }
// 3
type Exclusive<T, T2 extends T> = T2 & Record<Exclude<keyof T2, keyof T>, never>;
// 更改以下函数的类型定义,让它的参数只允许严格SomeType类型的值
function takeSomeTypeOnly<T extends SomeType>(x: Exclusive<SomeType, T>) { return x }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#第8道
type NonEmptyArray<T> = T[] & { 0: T };
const a: NonEmptyArray<string> = [] // 将出现编译错误
const b: NonEmptyArray<string> = ['Hello TS'] // 非空数据,正常使用
1
2
3
4
2
3
4
#第9道
type InferToString<T> = T extends string ? `${T}` : never;
type JoinStrArray<
Arr extends string[],
Separator extends string,
Result extends string = ''
> = Arr extends [infer S1, ...infer S2] ?
S2 extends [] ? `${Result}${InferToString<S1>}`
: S2 extends string[] ? JoinStrArray<S2, Separator, `${Result}${InferToString<S1>}${Separator}`> : never
: Result;
// 测试用例
type Names = ["Sem", "Lolo", "Kaquko"]
type NamesComma = JoinStrArray<Names, ","> // "Sem,Lolo,Kaquko"
type NamesSpace = JoinStrArray<Names, " "> // "Sem Lolo Kaquko"
type NamesStars = JoinStrArray<Names, "⭐️"> // "Sem⭐️Lolo⭐️Kaquko"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#第10道
type TrimLeft<V extends string> = V extends `${infer S1}${infer S2}` ? S1 extends ' ' ? TrimLeft<S2> : `${S1}${S2}` : V;
type TrimRight<V extends string, Result extends string = ''> = V extends `${infer S1}${infer S2}` ? S1 extends ' ' ? Result : TrimRight<S2, `${Result}${S1}`> : Result;
type Trim<V extends string> = TrimRight<TrimLeft<V>>;
// 测试用例
type B = Trim<' semlinker '>
//=> 'semlinker'
1
2
3
4
5
6
7
2
3
4
5
6
7
#第11道
type IsEqual<A, B> = [A] extends [B] ? [B] extends [A] ? true : false : false;
// 测试用例
type E0 = IsEqual<1, 2>; // false
type E1 = IsEqual<{ a: 1 }, { a: 1 }> // true
type E2 = IsEqual<[1], []>; // false
1
2
3
4
5
6
2
3
4
5
6
#第12道
type Head<T extends Array<any>> = T[0] extends undefined ? never : T[0];// 你的实现代码
// 测试用例
type H0 = Head<[]> // never
type H1 = Head<[1]> // 1
type H2 = Head<[3, 2]> // 3
1
2
3
4
5
6
2
3
4
5
6
#第13道
type Tail<T extends Array<any>> = T extends [infer L, ...infer R] ? R : [];
// 测试用例
type T0 = Tail<[]> // []
type T1 = Tail<[1, 2]> // [2]
type T2 = Tail<[1, 2, 3, 4, 5]> // [2, 3, 4, 5]
1
2
3
4
5
6
2
3
4
5
6
#第14道
type Unshift<T extends any[], E> = [E, ...T];
// 测试用例
type Arr0 = Unshift<[], 1>; // [1]
type Arr1 = Unshift<[1, 2, 3], 0>; // [0, 1, 2, 3]
1
2
3
4
5
2
3
4
5
#第15道
type Shift<T extends any[]> = T extends [infer T1, ...infer T2] ? T2 : [];
// 测试用例
type S0 = Shift<[1, 2, 3]> // [2, 3]
type S1 = Shift<[string,number,boolean]> // [number,boolean]
1
2
3
4
5
2
3
4
5
#第16道
type Push<T extends any[], V> = [...T, V];
// 测试用例
type Arr0 = Push<[], 1> // [1]
type Arr1 = Push<[1, 2, 3], 4> // [1, 2, 3, 4]
1
2
3
4
5
2
3
4
5
#第17道
type Includes<T extends Array<any>, E> = E extends T[number] ? true : false;
type I0 = Includes<[], 1> // false
type I1 = Includes<[2, 2, 3, 1], 2> // true
type I2 = Includes<[2, 3, 3, 1], 1> // true
1
2
3
4
5
2
3
4
5
#第18道
// 来自https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
// 测试用例
type U0 = UnionToIntersection<string | number> // never
type U1 = UnionToIntersection<{ name: string } | { age: number }> // { name: string; } & { age: number; }
1
2
3
4
5
6
2
3
4
5
6
#第19道
type Person = {
id: string;
name: string;
age: number;
from?: string;
speak?: string;
};
type OptionalKeys<T> = {
[key in keyof T]: T extends Record<key, T[key]> ? never : key;
}[keyof T];
type PersonOptionalKeys = OptionalKeys<Person> // "from" | "speak"
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
#第20道
type Curry<
F extends (...args: any[]) => any,
P extends any[] = Parameters<F>,
R = ReturnType<F>
> = P extends [infer P1, ...infer P2] ?
(P2 extends [] ? (arg: P[0]) => R : (arg: P[0]) => Curry<F, P2, R>)
: () => R;
type F0 = Curry<() => Date>; // () => Date
type F1 = Curry<(a: number) => Date>; // (arg: number) => Date
type F2 = Curry<(a: number, b: string) => Date>; // (arg_0: number) => (b: string) => Date
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
#第21道
type Foo = {
a: number;
b: string;
};
type Bar = {
b: number;
};
type Merge<FirstType, SecondType> = Omit<FirstType, Extract<keyof FirstType, keyof SecondType>> & SecondType;
const ab: Merge<Foo, Bar> = { a: 1, b: 2 };
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
#第22道
type Responder = {
text?: () => string;
json?: () => string;
secure?: boolean;
};
type RequireAtLeastOne<
ObjectType,
KeysType extends keyof ObjectType = keyof ObjectType,
> = Omit<ObjectType, KeysType> & {
[key in KeysType]: Required<Pick<ObjectType, key>> & Partial<Omit<ObjectType, key>>
}[KeysType];
type B = RequireAtLeastOne<Responder, 'text' | 'json'>;
// 表示当前类型至少包含 'text' 或 'json' 键
const responder: RequireAtLeastOne<Responder, 'text' | 'json'> = {
json: () => '{"message": "ok"}',
secure: true
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#第23道
interface Foo {
[key: string]: any;
[key: number]: any;
bar(): void;
}
type RemoveIndexSignature<T> = {
[key in keyof T as string extends key ? never : number extends key ? never : key]: T[key]
}
type FooWithOnlyBar = RemoveIndexSignature<Foo>; //{ bar: () => void; }
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
#第24道
type Foo = {
readonly a: number;
readonly b: string;
readonly c: boolean;
};
type CreateMutable<T> = {
-readonly [key in keyof T]: T[key];
};
type Mutable<T, Keys extends keyof T = keyof T> = CreateMutable<Pick<T, Keys>> & Omit<T, Keys>;
const mutableFoo: Mutable<Foo, 'a'> = { a: 1, b: '2', c: true };
mutableFoo.a = 3; // OK
mutableFoo.b = '6'; // Cannot assign to 'b' because it is a read-only property.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#第25道
type IsUnion<T, U = T> = U extends infer P ? [T] extends [P] ? false : true : false;
type I0 = IsUnion<string|number> // true
type I1 = IsUnion<string|never> // false
type I2 =IsUnion<string|unknown> // false
1
2
3
4
5
2
3
4
5
#第26道
type IsNever<T> = [T] extends [never] ? true : false;
type I0 = IsNever<never> // true
type I1 = IsNever<never | string> // false
type I2 = IsNever<null> // false
1
2
3
4
5
2
3
4
5
#第27道
type Reverse<
T extends Array<any>,
R extends Array<any> = []
> = T extends [infer T1, ...infer T2] ? Reverse<T2, [T1, ...R]> : R;
type R0 = Reverse<[]> // []
type R1 = Reverse<[1, 2, 3]> // [3, 2, 1]
1
2
3
4
5
6
7
2
3
4
5
6
7
#第28道
type Item = 'semlinker,lolo,kakuqo';
type Split<
S extends string,
Delimiter extends string,
Result extends string[] = []
> = S extends `${infer S1}${Delimiter}${infer S2}` ?
S2 extends `${infer S3}${Delimiter}${infer S4}` ?
Split<S2, Delimiter, [...Result, S1]> : [...Result, S1, S2] : [S];
type ElementType = Split<Item, ','>; // ["semlinker", "lolo", "kakuqo"]
// 第二种
type Split<
S extends string,
Delimiter extends string
> = S extends `${infer S1}${Delimiter}${infer S2}` ? [S1, ...Split<S2, Delimiter>] : [S];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#第29道
type ToPath<S extends string> = S extends `${infer S1}.${infer S2}` ?
S1 extends `${infer S3}[${infer S4}]` ? [S3, S4, ...ToPath<S2>] : [S1, ...ToPath<S2>]
: [S];
type A = ToPath<'foo.bar.baz'> //=> ['foo', 'bar', 'baz']
type B = ToPath<'foo[0].bar.baz'> //=> ['foo', '0', 'bar', 'baz']
1
2
3
4
5
6
2
3
4
5
6
#第30道
declare const config: Chainable
type Chainable<T = {}> = {
option<K extends string, V>(key: K, value: V): Chainable<T & { [P in K]: V }>;
get(): { [P in keyof T]: T[P] };
}
const result = config
.option('age', 7)
.option('name', 'lolo')
.option('address', { value: 'XiaMen' })
.get()
type ResultType = typeof result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14