入门
栗子
export type SetState<S extends Record<string, any>> = <K extends keyof S>(
state: Pick<S, K> | null | ((prevState: Readonly<S>) => Pick<S, K> | S | null),
) => void;
解析:
这段代码定义了一个名为
SetState
的类型:去掉一些类型定义之后,就是export type SetState = (state) => void
SetState
是个函数签名,返回为空(void
)
<S extends Record<string, any>>
是个类型参数Record<string, any>
是个泛型类型,必须是个对象,其中的键是字符串,而值可以是任意类型。extends
:继承,意味着S
也必须是个对象,其中的键是字符串,而值可以是任意类型
<K extends keyof S>
也是个类型参数
keyof
是一个关键字,用于获取一个类型的所有属性名(键)组成的联合类型- 这段代码意味着
K
必须是S
的属性名组成的联合类型
state: Pick<S, K> | null | ((prevState: Readonly<S>) => Pick<S, K> | S | null)
: 定义参数state
的类型Pick<S, K>
:表示部分状态更新,仅更新对象S
中指定的键K
。Pick
是一个内置类型,用于从一个类型中选取部分属性来创建一个新的类型。Pick 类型允许您从原始类型中选择一些属性,而忽略其他属性。null
:表示将状态重置为 null。(prevState: Readonly<S>) => Pick<S, K> | S | null
:这是一个函数- 接受
prevState
作为参数,Readonly
是一个内置类型 ,用于创建一个只读版本的指定类型 - 返回部分状态更新(
Pick<S, K>
)、整个状态S
,或者null
- 接受
泛型
泛型
(Generics)是编程语言中的一个 概念它允许您在定义函数、类或类型时不指定具体的数据类型,而是在使用这些函数、类或类型时再传入实际的数据类型
在
TypeScript
中,泛型通过使用类型参数 来实现。泛型能够提高代码的可重用性和灵活性,因为它允许您编写能够处理多种数据类型的通用代码。
类型参数可以在函数、类或类型的定义中使用,以代表一个未知的数据类型。当您使用带有泛型的函数、类或类型时,您需要提供实际的数据类型来替换这些类型参数,从而在特定的上下文中确定具体的数据类型。
类型参数
类型参数
的作用类似于函数参数,但它们不是用来传递实际的值,而是用来指定某些类型的 占位符。定义泛型使用,也可以叫作
泛型参数
当您在调用泛型函数或实例化泛型类时,会提供实际的类型来替代这些类型参数,从而确定函数的行为或类的属性和方法的类型。
在
TypeScript
中,使用尖括号(< >
)来声明类型参数在条件类型中,也可以使用 infer 作类型参数补充
// 类型参数T、U
type ExtractSelf3<T, U> = T extends U ? U : T;
// 类型参数T、infer U相当于补充声明类型参数U
type ExtractSelf<T> = T extends infer U ? U : T;
// 报错:Cannot find name 'U'. 只传入类型参数T,未对U进行声明
type ExtractSelf2<T> = T extends U ? U : T;
内置类型
一、基本类型
number
: 表示数值类型。string
: 表示字符串类型。boolean
: 表示布尔类型。null
: 表示空值或空引用。undefined
: 表示未定义的值。void
: 表示没有返回值的函数。symbol
: 表示唯一的符号值。
二、复合类型
Array<T>
: 表示数组类型,且是一个由类型 T 的元素组成的数组Tuple
: 表示固定长度的数组,允许元素的类型是不同的。Object
: 表示任意 JavaScript 对象类型。Function
: 表示函数类型。Enum
: 表示枚举类型。Class
: 表示类类型。
1 - Array<T>
数组
申明一个由类型
T
的元素组成的数组(数组里的元素类型都为T
)
// 声明变量
const numbers: Array<number> = [1, 2, 3, 4, 5];
const names: Array<string> = ["Alice", "Bob", "Charlie"];
// 函数参数/返回值
function doubleNumbers(numbers: Array<number>): Array<number> {
return numbers.map(num => num * 2);
}
const doubled = doubleNumbers([1, 2, 3, 4]);
// 泛型函数
function reverseArray<T>(array: Array<T>): Array<T> {
return array.reverse();
}
const reversedNumbers = reverseArray([1, 2, 3]);
const reversedStrings = reverseArray(["apple", "banana", "cherry"]);
// T也可以是变量别名,此处为一个二维数组(一维为Point,二维为[number, string]组成的长度为2的数组)
type Point = [number, string];
const coordinates: Array<Point> = [[1, '2'], [3, '4'], [5, '6']];
// 使用Array[number],可直接返回联合类型
type Point2<M extends string[]> = M[number];
type P = Point2<['small', 'medium', 'large']>; // 'small' | 'medium' | 'large'
// eg: 通过Array[number]和模版字符串``定义BEM类型
type IsNever<T> = [T] extends [never] ? true : false
type IsUnion<U> = IsNever<U> extends true ? "" : U
type BEM<B extends string, E extends string[], M extends string[]> = `${B}${IsUnion<`__${E[number]}`>}${IsUnion<`--${M[number]}`>}`
type ClassNames1 = BEM<'btn', ['price'], []> // 'btn__price'
type ClassNames2 = BEM<'btn', ['price'], ['warning', 'success']> // 'btn__price--warning' | 'btn__price--success'
type ClassNames3 = BEM<'btn', [], ['small', 'medium', 'large']> // 'btn--small' | 'btn--medium' | 'btn--large'
type ClassNames4 = BEM<'btn', ['primary', 'danger'], ['small', 'medium', 'large']> // "btn__primary--small" | "btn__primary--medium" | "btn__primary--large" | "btn__danger--small" | "btn__danger--medium" | "btn__danger--large"
2 - Tuple元组 - 特殊数组
申明一个可以由不同类型组成,长度固定的数组
// 定义一个元组类型,包含不同类型的元素
type MixedTuple = [number, string, boolean];
// 声明一个符合 MixedTuple 类型的元组变量
const mixedValues: MixedTuple = [42, "hello", true];
// 访问元组中的元素
const firstElement = mixedValues[0]; // 42
const secondElement = mixedValues[1]; // "hello"
const thirdElement = mixedValues[2]; // true
不限类型不限数组长度,可以使用any[]
3 - Object对象
const person: object = { name: "Alice", age: 30 };
const greet: object = function(name: string) {
console.log(`Hello, ${name}!`);
};
4 - Function函数
// 直接使用别名
type MathOperation = Function;
// 直接函数写法加返回值
const greet = (name: string): void => {
console.log(`Hello, ${name}!`);
};
5 - Enum枚举
将一组相关的命名常量组织起来,使代码更加可读和维护
type Red = 'red';
type Green = 'green';
type Blue = 'blue';
enum Color {
Red,
Green,
Blue,
}
const selectedColor: Color = Color.Green;
if (selectedColor === Color.Green) {
console.log("Selected color is green.");
}
6 - Class类
创建一个类
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
}
const alice = new Person("Alice", 30);
alice.greet(); // Output: "Hello, my name is Alice and I'm 30 years old."
三、类型工具
Record<K, T>
: 创建一个以类型K
为键,类型T
为值的对象类型。Readonly<T>
: 创建只读版本的类型。Partial<T>
: 复制T
类型的所有属性,并将所有属变成可选属性Required<T>
: 复制T
类型的所有属性,并将所有属变成必选属性Pick<T, K>
: 子集,从类型T
中选取指定属性K
。Omit<T, K>
: 排除,从类型T
中排除属性K
。Exclude<T, U>
: 从类型T
中排除可以赋值给类型U
的类型。ReturnType<T>
: 获取函数类型T
的返回类型。Union
: 用于创建联合类型,例如 number | string。或
的关系Intersection
: 用于创建交叉类型,表示同时具有多个类型的值。且
的关系
1 - Record<K, T>
创建对象
创建一个以类型
K
为键,类型T
为值的对象类型。
type Person = {
name: string;
age: number;
};
// 使用 Record 创建一个名为 People 的类型,键为 string 类型,值为 Person 类型
type People = Record<string, Person>;
const people: People = {
alice: { name: 'Alice', age: 30 },
bob: { name: 'Bob', age: 25 },
};
console.log(people.alice); // Output: { name: 'Alice', age: 30 }
console.log(people.bob); // Output: { name: 'Bob', age: 25 }
2 - Readonly<T>
只读
type Person = {
name: string,
age: number
}
// 申请一个只读类型
type ReadonlyPerson = Readonly<Person>;
const person: ReadonlyPerson = {
name: 'LiLei',
age: 18
}
// 下面的代码将会报错,因为属性是只读的
person.age = 19;
3 - Partial<T>
可选
复制
T
类型的所有属性,并将所有属变成可选属性
type Person = {
name: string;
age: number;
address: string;
};
// 复制 Person 类型中的所有属性,并变为可选
type PartialPerson = Partial<Person>;
// 不会报错
const partialAlice: PartialPerson = {
name: "Alice",
};
// 会报错:Type '{ name: string; }' is missing the following properties from type 'Person': age, address
const alice: Person = {
name: 'Alice'
}
4 - Required<T>
必选
与
Partial<T>
相反,复制T
类型的所有属性,并将所有属变成必选属性
type PartialPerson = {
name?: string;
age?: number;
address?: string;
};
// 将 PartialPerson 类型中的所有属性变为必需
type RequiredPerson = Required<PartialPerson>;
// 会报错:Type '{ name: string; }' is missing the following properties from type 'Required<PartialPerson>': age, address
const requiredAlice: RequiredPerson = {
name: "Alice",
};
// 不会报错
const alice: Person = {
name: "Alice",
}
5 - Pick<T, K>
选属性子集
Pick<T, K>
从类型T
中选出K
,K
可以是字符串字面量类型或联合类型
type Person = {
name: string;
age: number;
address: string;
};
// 从 Person 类型中选择 name 和 age 属性
type PersonInfo = Pick<Person, 'name' | 'age'>;
const personInfo: PersonInfo = {
name: 'Alice',
age: 30,
};
6 - Omit<T, K>
排除属性子集
Omit<T, K>
从类型T
中排除出K
, 生成新类型,K
可以是字符串字面量类型或联合类型
type Person = {
name: string;
age: number;
address: string;
};
// 错误示例:K是字符串字面量类型或联合类型,而不是数组
type InvalidOmit = Omit<Person, ['age', 'address']>;
// 正确写法
type PersonWithoutAgeAndAddress = Omit<Person, 'age' | 'address'>;
7 - Exclude<T, U>
排除类型子集
Exclude<T, U>
从类型T
中排除出可以赋值给U
的类型,生成新类型
Omit<T, K>
只能用于排除属性Exclude<T, U>
只能用于排除类型
8 - ReturnType<T>
返回值的类型
type MyFunction = () => string;
// 获取 MyFunction 的返回值类型
type ResultType = ReturnType<MyFunction>;
const myFunction: MyFunction = () => {
return "Hello, world!";
};
const result: ResultType = myFunction();
console.log(result); // Output: "Hello, world!"
9 - 联合类型
将多个类型组合在一起,以表示一个值可以是这些类型中的任意一个,使用
|
连接
// 声明一个联合类型
type NumberOrString = number | string;
// 变量可以是 number 或 string 类型
let value: NumberOrString;
value = 10; // OK,value 是 number 类型
value = "hello"; // OK,value 是 string 类型
value = true; // Error,value 不能是 boolean 类型
// 这也是个联合类型,只是指定了类型值
type labelType = 'template' | 'rich';
let label: labelType;
label = 'template'; // OK
label = 'rich'; // OK
label = 'custom'; // Error,'custom' 不是有效的值
2个映射类型生成的联合类型,不是具有两个映射类型属性合成的属性,而应该理解为:具有映射类型A的所有属性,或者具有映射类型B的所有属性
interface A {
name: string;
age: string;
}
interface B {
name: string;
height: number;
}
type C = A | B; // C可能是{name, age},或者是{name, height},而不是{name, age, height}
// 不报错
const c: C = {
name: 'Alice',
age: "18",
height: 178
}
// 会报错:因为这里不知道c是具有A类型 还是B类型
// c.height
// 方法一:类型判断
if ('height' in c) {
c.height
}
// 方法二:使用as断言
(a as B).height
10 - 交叉类型
将多个类型合并成一个新的类型,新类型将拥有所有参与合并的类型的属性和方法。使用
&
符号连接
// 定义两个类型
type Person = {
name: string;
};
type Employee = {
employeeId: number;
};
// 创建交叉类型,合并两个类型的属性
type EmployeePerson = Person & Employee;
const employeePerson: EmployeePerson = {
name: "John",
employeeId: 123
};
四、特殊类型
any
: 表示任意类型,允许进行动态类型转换。never
: 表示永远不会出现的值的类型,通常用于表示错误或异常情况。unknown
: 类似于 any,但更安全,需要进行类型检查或类型断言。this
: 表示函数中的 this 上下文类型。
1 - any任意类型
使用
any
类型可以绕过TypeScript
的类型检查
,但同时也丧失了类型安全性。应尽量避免使用
any
类型,因为它会减弱类型检查的优势
2 - never永不会出现的类型
// 抛出异常的函数的返回值
function throwError(message: string): never {
throw new Error(message);
}
// 该函数永远不会正常返回,它总是会抛出异常
throwError("Something went wrong");
// 永远无返回值的函数
function infiniteLoop(): never {
while (true) {
// 无限循环,永远不会返回结果
}
}
// 函数检查
type Result<T> = T extends number ? string : never;
const numberValue: Result<number> = "42";
// Error: Type 'string' is not assignable to type 'never'.
const stringValue: Result<string> = "42";
// 联合类型的不可达分支
type Fruit = "apple" | "banana" | "orange";
function getFruitColor(fruit: Fruit): string {
switch (fruit) {
case "apple":
return "red";
case "banana":
return "yellow";
case "orange":
return "orange";
default:
const _exhaustiveCheck: never = fruit;
return _exhaustiveCheck; // Error,该分支是不可达的
}
}
3 - unknow未知类型
使用
unknow
类型必须进行类型检查
或使用类型断言
,否则无法正确访问其属性和方法
let userInput: unknown;
userInput = "Hello, TypeScript!";
// 需要进行类型检查或类型断言
if (typeof userInput === "string") {
console.log(userInput.toUpperCase());
}
// 直接访问会报错
console.log(userInput.toUpperCase());
// 也可以使用 as 类型断言
console.log((userInput as string).toUpperCase());
// 将unknow赋值给其他类型, 使用类型断言将 unknown 转换为 string
let value: unknown = "Hello";
let stringValue: string = value as string;
关键字和操作符
类型相关的关键字有:
type
别名interface
接口extends
继承/条件类型infer
类型推断in
:遍历对象属性的键declare
全局声明class
类enum
枚举|
联合类型&
交叉类型<>
/as
类型断言<T>
泛型
声明属性状态的关键字有:
readonly
只读public
公共,默认的状态,可以不写private
私有,只能在类的内部访问protected
受保护的,只能在类的内部和派生类中访问,不能在类的外部访问static
静态的,属于类本身而不是类的实例。静态属性可以在类的内部和外部通过类名访问
类型相关的操作符有:
keyof
: 获取类型的属性名组成的联合类型typeof
: 获取值的类型
一、类型断言
明确指示某个值的类型,从而绕过
TypeScript
对该值的类型检查器
const value: any = "Hello, TypeScript!";
// 使用<>尖括号
const length: number = (<string>value).length;
// 使用as
const length: number = (value as string).length;
二、type和interface的区别
1 - 语法
type T = xxx
interface xxx {}
type PersonType = {
name: string;
age: number;
};
interface PersonInterface {
name: string;
age: number;
}
2 - 定义的内容
type
可以用于定义类、对象的类型,也可以不是interface
只能用于定义类、对象的类型
3 - 扩展方式
type
使用|
联合类型 或&
交叉类型interface
使用extends
type Cat = Animal & { breed: string };
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
三、extends继承/条件类型
typescript
中,T extends U
可以用作类型继承,也可以用于条件类型的判断T extends U
用作条件类型的判断时,有多种解释:T
: 非固定值类型,U
:非固定值类型 =>T
是否是U
的子类型T
: 固定值类型,U
:非固定值类型 =>T
值是否属于U
类型T
: 固定值类型,U
:固定值类型- 数组:基于元素的位置和类型比较
- 对象:基于属性和属性值
- 其他常量:
T
值是否等于U
值
// 类型继承
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
/************ 条件类型 *********/
// 非固定值类型:看是否是子集
type AA = (number | string) extends string ? true : false; // false
type AAA = string extends (number | string) ? true : false; // true
// 在数组中比较: 基于元素的位置和类型的
// ['','']是否是['','',''] 部分和 ...any 部分
type C = ['', ''] extends ['', '', '', ...any] ? true : false; // false
// ['','','']是否是['',''] 部分和 ...any 部分
type CC = ['', '',''] extends ['', '', ...any] ? true : false; // true
// 对象内:基于属性和属性值
type CCC = { name: 'Alice' } extends { name: 'Alice', age: 18 } ? true : false; // false
type CCCC = {name: 'Alice', age: 18} extends {name: 'Alice'} ? true : false; // true
// 此处传入<4, 6>,`T extends R['length']`判断为4是否等于0
type GreaterThan<T extends number, U extends number, R extends any[] = []> =
T extends R['length']
? false
: U extends R['length']
? true
: GreaterThan<T, U, [...R, 1]>
const varA: GreaterThan<4, 6> = true;
四、infer类型推断
用于条件类型 extends中进行类型的推断操作
- 与
extends
一起使用- 部分功能=函数的类型参数,作类型补充
- 比直接写
<>类型参数
更友好的一点是:可以用作动态类型参数 - 可以看作实现类似高阶函数的一根镙丝钉:比如,
- 类型
A
的类型参数B
,类型参数B
又是个带类型参数C
的类型,infer
可以对C
做类型推断 - 类型
A
的类型参数B
,类型参数B
又是个带参数的函数D
,infer
可以对D
的参数做类型推断
- 类型
// 类型参数T、infer U相当于是U的类型声明
type ExtractSelf<T> = T extends infer U ? U : T;
// 类型参数T、U
type ExtractSelf3<T, U> = T extends U ? U : T;
// 报错:Cannot find name 'U'. 只传入类型参数T,未对U进行声明
type ExtractSelf2<T> = T extends U ? U : T;
type GetValueType<T> = T extends Promise<infer R> ? R : never;
type PromiseType = Promise<number[]>;
type Res = GetValueType<PromiseType>; // number[]
type FunctionType<T> = T extends (...args: infer Args) => infer Result ? [Args, Result] : never;
// 这么写,FunctionType<typeof greet>会报错:Generic type 'FunctionType' requires 3 type argument(s).
// type FunctionType<T, Args extends any[], Result> = T extends (...args: Args) => Result ? [Args, Result] : never;
function greet(name: string, age: number): string {
return `Hello, ${name}! You are ${age} years old.`;
}
type GreetFunctionType = FunctionType<typeof greet>; // [ [string, number], string ]
let gt: GreetFunctionType = [['Alice', 18], 'Hello, Alice! You are 18 years old'];
五、in遍历对象属性名
in
:遍历对象属性的键
// 定义一个映射类型,遍历 T 的所有属性
type SharedProperties<T> = {
[K in keyof T]: T[K];
};
六、declare全局声明
在
TypeScript
中,.d.ts
文件用于编写声明文件,以描述 模块、库或对象的类型信息
TypeScript
会自动识别并应用这些声明
// 声明变量
declare const MY_GLOBAL: string;
// 声明模块
declare module "moment" {
import moment from 'moment';
export default moment;
}
// 声明方法
declare function greet(name: string): string;