2020 年 写了 「《重学 TS 1.0》」 系列文章,共写了 「50」 几篇。TS 发展很快,越来越多的项目都开始使用 TS 来开发,目前 TS 的版本已经更新到了 4.4 版本。
近期 在团队内培训 TS 专题,发现挺多小伙伴对 TS 掌握得并不是很好,对 TS 类型编程(类型体操)的相关概念理解并不是很清楚。所以阿宝哥决定开始更新 「《重学 TS 2.0》」,这个专题会对之前的文章进行补充与更新,同时会介绍 TS 4.0 以上的新特性。同时,也会重点介绍 TS 中的一些核心概念,比如类型安全、结构类型系统、映射类型、泛型、逆变/协变等。
在 「《重学 TS 2.0》」 系列出场前,大家先来做几道题热热身,感兴趣的小伙伴可以动手试试看。
type User = {
id: number;
kind: string;
};
function makeCustomer<T extends User>(u: T): T {
// Error(TS 编译器版本:v4.4.2)
// Type '{ id: number; kind: string; }' is not assignable to type 'T'.
// '{ id: number; kind: string; }' is assignable to the constraint of type 'T',
// but 'T' could be instantiated with a different subtype of constraint 'User'.
return {
id: u.id,
kind: 'customer'
}
}
以上代码为什么会提示错误,应该如何解决上述问题?
本道题我们希望参数 a
和 b
的类型都是一致的,即 a
和 b
只能同时为 number
或 string
类型。当它们的类型不一致时,TS 类型检查器能自动提示对应的错误信息。
function f(a: string | number, b: string | number) {
if (typeof a === 'string') {
return a + ':' + b; // no error but b can be number!
} else {
return a + b; // error as b can be number | string
}
}
f(2, 3); // Ok
f(1, 'a'); // Error
f('a', 2); // Error
f('a', 'b') // Ok
在 [掌握 TS 这些工具类型,让你开发事半功倍] 这篇文章中,阿宝哥介绍了 TS 内置的工具类型:Partial<T>
,它的作用是将某个类型里的属性全部变为可选项 ?
。
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
// lib.es5.d.ts
type Partial<T> = {
[P in keyof T]?: T[P];
};
那么如何定义一个 SetOptional
工具类型,支持把给定的 keys 对应的属性变成可选的?对应的使用示例如下所示:
type Foo = {
a: number;
b?: string;
c: boolean;
}
// 测试用例
type SomeOptional = SetOptional<Foo, 'a' | 'b'>;
// type SomeOptional = {
// a?: number; // 该属性已变成可选的
// b?: string; // 保持不变
// c: boolean;
// }
在实现 SetOptional
工具类型之后,你还可以继续实现 SetRequired
工具类型,利用它可以把指定的 keys 对应的属性变成必填的。对应的使用示例如下所示:
type Foo = {
a?: number;
b: string;
c?: boolean;
}
// 测试用例
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;
// type SomeRequired = {
// a?: number;
// b: string; // 保持不变
// c: boolean; // 该属性已变成必填
// }
Pick<T, K extends keyof T>
的作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false
};
那么如何定义一个 ConditionalPick
工具类型,支持根据指定的 Condition
条件来生成新的类型,对应的使用示例如下:
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, string>;
//=> {a: string}
定义一个工具类型 AppendArgument
,为已有的函数类型增加指定类型的参数,新增的参数名是 x
,将作为新函数类型的第一个参数。具体的使用示例如下所示:
type Fn = (a: number, b: string) => number
type AppendArgument<F, A> = // 你的实现代码
type FinalFn = AppendArgument<Fn, boolean>
// (x: boolean, a: number, b: string) => number
定义一个 NativeFlat 工具类型,支持把数组类型拍平(扁平化)。具体的使用示例如下所示:
type NaiveFlat<T extends any[]> = // 你的实现代码
// 测试用例:
type NaiveResult = NaiveFlat<[['a'], ['b', 'c'], ['d']]>
// NaiveResult的结果: "a" | "b" | "c" | "d"
在完成 NaiveFlat
工具类型之后,在继续实现 DeepFlat
工具类型,以支持多维数组类型:
type DeepFlat<T extends any[]> = unknown // 你的实现代码
// 测试用例
type Deep = [['a'], ['b', 'c'], [['d']], [[[['e']]]]];
type DeepTestResult = DeepFlat<Deep>
// DeepTestResult: "a" | "b" | "c" | "d" | "e"
使用类型别名定义一个 EmptyObject
类型,使得该类型只允许空对象赋值:
type EmptyObject = {}
// 测试用例
const shouldPass: EmptyObject = {}; // 可以正常赋值
const shouldFail: EmptyObject = { // 将出现编译错误
prop: "TS"
}
在通过 EmptyObject
类型的测试用例检测后,我们来调整一下 takeSomeTypeOnly
函数的类型定义,让它的参数只允许 SomeType 类型(严格模式)的值。具体的使用示例如下所示:
type SomeType = {
prop: string
}
// 更改以下函数的类型定义,让它的参数只允许严格SomeType类型的值
function takeSomeTypeOnly(x: SomeType) { return x }
// 测试用例:
const x = { prop: 'a' };
takeSomeTypeOnly(x) // 可以正常调用
const y = { prop: 'a', addditionalProp: 'x' };
takeSomeTypeOnly(y) // 将出现编译错误
定义 NonEmptyArray
工具类型,用于确保数据非空数组。
type NonEmptyArray<T> = // 你的实现代码
const a: NonEmptyArray<string> = [] // 将出现编译错误
const b: NonEmptyArray<string> = ['Hello TS'] // 非空数据,正常使用
❝提示:该题目有多种解法,感兴趣小伙伴可以自行尝试一下。
❞
定义一个 JoinStrArray
工具类型,用于根据指定的 Separator
分隔符,对字符串数组类型进行拼接。具体的使用示例如下所示:
type JoinStrArray<Arr extends string[], Separator extends string, Result extends string = ""> = // 你的实现代码
// 测试用例
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"
实现一个 Trim
工具类型,用于对字符串字面量类型进行去空格处理。具体的使用示例如下所示:
type Trim<V extends string> = // 你的实现代码
// 测试用例
Trim<' semlinker '>
//=> 'semlinker'
❝提示:可以考虑先定义 TrimLeft 和 TrimRight 工具类型,再组合成 Trim 工具类型。
❞
如果以上题目你都能轻松作答,那么你的 TS 水平应该还不错。当然如果你做完上述题目之后,还觉得意犹未尽的话,可以继续做在线的 TS 练习题, https://typescript-exercises.github.io/。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8