函数重载
使用场景
当我们在调用函数需要根据不同的传参、和参数类型的不同返回不同的结果时,使用函数重载可以给出更友好的提示
function print(text: string, length: number): number;
function print(text: string): string;
function print(text: string, length: number){
if(typeof text === 'string' && length){
return length
}
return text
}
print('111', 111)
print('222')
这里假设我们有这么一个需求:
有一个包含userInfo
和loading
的State
interface IState {
loading: boolean;
userInfo: {
id: string;
name: string;
}
}
const defaultState: IState = {
loading: false,
userInfo: {
id: '1',
name: 'huolihua'
}
}
还有一个useAppState
用来查询该State
,查询有两种方式:
const state = useAppState() // defaultState
const userInfo = useAppState(state => state.userInfo) // userInfo
在实现useAppState
时如何使用Ts
给出比较友好的提示呢?像下图这样:
实现:
function useAppState<T>(selector: (state: IState) => T): T;
function useAppState(): IState;
function useAppState<T>(selector?: (state: IState) => T) {
if (!selector) {
return defaultState
}
return selector(defaultState)
}
const state = useAppState() // defaultState
const userInfo = useAppState(state => state.userInfo) // userInfo
可辨识联合
先看下面这块代码
interface ICat {
color: string;
move: string;
}
interface IDog {
eat: string;
cry: string;
}
function print(animal: ICat | IDog) {
console.log(animal.color) //报错
}
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法,我们可以通过
为联合类型增加一个可辨识的特征type
如下:
interface ICat {
color: string;
move: string;
type: 'cat'
}
interface IDog {
eat: string;
cry: string;
type: 'dog'
}
这个时候我们增加了一个type
来区分两个类型,通过type我们可以做以下判断来访问正确的属性;而且这个时候ts会自动为我们推导出正确的类型。
function print(animal: ICat | IDog) {
if(animal.type === 'cat'){
console.log(animal.color)
}
}
<img src="https://huolihua-1256524053.cos.ap-nanjing.myqcloud.com/image-20220308155731964.png" alt="image-20220308155731964" style="zoom:67%;" />
可辨识联合给我的感觉也就是一种类型保护机制。
Ts类型保护的使用
我所知道的类型保护有以下几种
- 使用
is
- 使用
in
,判断一个属性是不是属于某一个对象 - 使用
typeof
- 使用
instanceof
,判断一个实例是不是属于某个类
先定义两种类型
interface ITeacher {
subject: string;
}
interface IStudent {
name: string;
age: string;
}
使用is:
function isTeacherWithIs(arg: ITeacher | IStudent): arg is ITeacher {
return (arg as ITeacher).subject !== undefined
}
function print(arg: ITeacher | IStudent) {
if (isTeacherWithIs(arg)) {
console.log(arg.subject)
}
}
使用in:
function isTeacherWithIn(arg: ITeacher | IStudent){
if('name' in arg){
// arg 是 IStudent
console.log(arg.age)
}
}
使用typeof:
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
这些
typeof
类型保护只有两种形式能被识别:typeof v === "typename"
和typeof v !== "typename"
,"typename"
必须是"number"
,"string"
,"boolean"
或"symbol"
。 但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
使用instanceof
,判断一个实例是不是属于某个类
interface Padder {
getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) { }
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) { }
getPaddingString() {
return this.value;
}
}
function getRandomPadder() {
return Math.random() < 0.5 ?
new SpaceRepeatingPadder(4) :
new StringPadder(" ");
}
// 类型为SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
padder; // 类型细化为'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder; // 类型细化为'StringPadder'
}
instanceof
的右侧要求是一个构造函数,TypeScript将细化为:
- 此构造函数的
prototype
属性的类型,如果它的类型不为any
的话- 构造签名所返回的类型的联合
以此顺序。
关于类型约束extends的使用
extends
约束可以做为条件判断来使用像这样:
type Fly<T> = T extends 'Bird' ? 'Yes' : 'No'
type isFlyWithBird = Fly<'Bird'> // Yes
type isFlyWithPig = Fly<'Pig'> // No
常见的使用场景比如网络请求中判断是不是分页?分页的话返回分页的数据结构,否则返回默认的结构
interface PageResponse<T> {
pageSize: number;
pageNumber: number;
pageTotal: number;
content: T[]
}
// T:自定义类型,P:是否分页
type ResponseData<T, P> = P extends true ? PageResponse<T> : T
我们模仿一个get请求:
function get<T, P = false>(target: ResponseData<T, P>): ResponseData<T, P> {
return target
}
调用一下试试
interface UserInfo {
name: string;
age: number;
}
const res = get<UserInfo, false>({ name: '会飞的Pig', age: 20 }) // res类型为UserInfo
const res2 = get<UserInfo, true>({
pageNumber: 0,
pageSize: 10,
pageTotal: 10,
content: [{ name: '会飞的Pig', age: 20 }]
}) // res2类型为PageResponse<UserInfo>
这个时候从请求结果中取值时Ts能帮我们推导出正确的类型:
类型递归
interface IPeople {
name: string;
age: number;
children: {
gender: string;
nation: string;
address: {
city: string;
}
}
}
思考一个问题:如何将IPeople
的属性提取出来作为类型约束呢? 像这种name | age | gender | nation
先看答案:
type Keys<T> =
T extends object ? { [K in keyof T]-?: K | Keys<T[K]> } [keyof T] : never;
// "name" | "age" | "children" | "gender" | "nation" | "address" | "city"
type NewType = Keys<IPeople>
先判断传进来的
IPeople
是不是对象,如果不是对象的话直接返回never
; 关于never
Ts官网是这样描述的 :“never
类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型”-
{[K in keyof T]-?: K | Keys<T[K]>}
返回一个新的对象如下图:<img src="https://huolihua-1256524053.cos.ap-nanjing.myqcloud.com/image-20220309142510592.png" alt="image-20220309142510592" style="zoom:67%;" />
拿到这个对象只要取出对象的键值就是我们想要的结果了,在JS中取对象的键值可以使用
Object[key]
在Ts中也是可以的,所以这里使用[keyof T]
来取新对象的键值。
keyof
的作用就是返回一个对象类型的键作为联合类型
interface Obj{
name: string;
age: number
}
type D = keyof Obj // 'name' | 'age'
type E = Obj['name' | 'age'] // string | number
in
用于取联合类型的值,主要用于数组和对象的构造
type name = 'firstName' | 'lastName';
type TName = {
[key in name]: string;
}; // {firstName: string; lastName: string}
-?
的作用是将映射类型的属性变为必选。Required
实现原理就是用它:
/**
* Make all properties in T required
*/
type Required<T> = {
[P in keyof T]-?: T[P];
};
常见的几种高级类型
Partial
: 使T类型的所有属性变为可选。
type Partial<T> = {
[P in keyof T]?: T[P];
};
主要还是使用keyof
拿到T类型的键然后使用in
构造一个新的对象
Readonly
: 使T类型的所有属性变为只读。
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
ReturnType
: 获取函数类型的返回类型
/**
* Obtain the return type of a function type
*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
关于infer
: 表示在 extends
条件语句中待推断的类型变量,但是只能在为true
的分支中使用;
Parameters
: 获取函数的参数类型
/**
* Obtain the parameters of a function type in a tuple
*/
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
Exclude
:从T中排除那些可赋给U的类型
/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;
Extract
: 从T中提取那些可以赋值给U的类型
/**
* Extract from T those types that are assignable to U
*/
type Extract<T, U> = T extends U ? T : never;
Omit
: 从对象T中去除满足U类型的属性
/**
* Construct a type with the properties of T except for those in type K.
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
Record
: 约束一个对象的key类型和value类型
/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};