TypeScript
17416字约58分钟
2025-02-11
TypeScript 有哪些常用类型?
详情
TypeScript 的常用类型包括:
- 基础类型:
string
、number
、boolean
、null
、undefined
、symbol
、bigint
- 复杂类型:
array
、tuple
、enum
、object
- 特殊类型:
any
、unknown
、never
、void
什么是 TypeScript 的对象类型?怎么定义对象类型?
详情
什么是 TypeScript 的对象类型?
在 TypeScript 中,对象类型用于描述非原始类型的值,比如具有特定结构的对象、数组和函数等。
如何定义对象类型? 我们可以通过 3 种主要方式来定义对象类型:匿名、类型别名、接口。
1)匿名对象
可以直接用类 JavaScript 的语法定义对象属性,示例如下:
function greet(person: { name: string; age: number }) {
return "Hello " + person.name;
}
该函数接受包含属性 name(必须是 string)和 age(必须是 number)的对象。
2)类型别名。通过 type
关键字来创建,它为一个特定的对象类型创建了一个新名称。示例如下:
type Person = {
name: string;
age: number;
};
function greet(person: Person) {
return "Hello " + person.name;
}
类型别名适用于复杂的类型组合,如联合类型、交叉类型或条件类型。
3)接口。通过 interface
关键字定义,示例如下:
interface Employee extends Person {
employeeId: number;
}
let jane: Employee = {
name: "Jane",
age: 25,
employeeId: 1234
};
接口还可以用于描述函数类型,示例如下:
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
};
什么是 TypeScript 的类型别名?怎么定义类型别名?
详情
类型别名(Type Alias)是 TypeScript 中用来给某种特定类型创建一个新的名字的功能。它主要用于简化或提高代码的可读性,特别是在处理复杂类型时。定义类型别名使用 type
关键字。
type Name = string; // 为 string 类型创建一个别名叫 Name
type Point = { // 为对象类型创建一个别名叫 Point
x: number;
y: number;
};
type ID = number | string; // 为联合类型创建一个别名叫 ID
什么是 TypeScript 的接口?怎么定义接口?
详情
接口(Interface)是 TypeScript 中用于定义对象结构的工具。它允许我们描述对象的形状,比如对象有哪些属性以及属性的类型。这有助于在代码中提高类型检查的精确度,让代码更为严谨。简单来说,接口就是一种声明,但不会在编译后生成任何代码。
定义接口很简单,使用 interface
关键字来声明接口,然后在大括号 {}
内描述属性和类型。比如:
interface Person {
name: string;
age: number;
greet: () => void;
}
在这个例子中,我们定义了一个名为 Person 的接口,它有三个属性:name 和 age 是字符串和数字类型,greet 是一个没有参数且返回值为 void 的函数。
TypeScript 的类型别名和接口有什么区别?
详情
TypeScript 的类型别名和接口都有助于定义复杂类型,但它们存在一些关键区别:
- 1)用途不同
- 2)扩展方式不同
- 3)合并机制不同
1)用途不同
类型别名(Type Aliases)可以用于定义原始类型、联合类型、元组以及复杂对象等各种类型。接口(Interfaces)则主要用于定义对象类型。
2)扩展方式不同
接口可以通过 extends 关键字进行扩展,而类型别名则需要使用交叉类型 & 来进行组合。
3)合并机制不同
接口支持声明合并,即可以多次声明同一个接口名称,它们会自动合并。而类型别名不支持这一点,重复声明同名的别名会导致编译错误。
TypeScript 中如何定义和导入模块?
详情
在 TypeScript 中,我们可以使用 import 和 export 关键字来定义和导入模块。具体地:
1)定义模块:我们可以将变量、函数、类以及接口等导出,使用 export 关键字。比如:
// myModule.ts
export const myVariable = 42;
export function myFunction() {
console.log('Hello, TypeScript!');
}
export class MyClass {
greet() {
console.log('Greetings!');
}
}
2)导入模块:我们可以使用 import 关键字来导入模块。比如:
// main.ts
import { myVariable, myFunction, MyClass } from './myModule';
console.log(myVariable); // 输出:42
myFunction(); // 输出:Hello, TypeScript!
const myClassInstance = new MyClass();
myClassInstance.greet(); // 输出:Greetings!
TypeScript 的类有哪些成员可见性?
详情
TypeScript 的类成员有三个主要的可见性修饰符:public、private 和 protected。
1)public: 默认的修饰符,表示类成员可以在任何地方访问,没有限制。
2)private: 表示类成员只能在声明它的类内部访问,不能在类的实例以及子类中访问。
3)protected: 表示类成员可以在声明它的类及其子类中访问,但不能在类的实例中访问。
还有一个特别的标记是 readonly,其作用是使类成员只能在声明时或在构造函数中赋值,随后变为只读。它可以与 public、private 或 protected 一起使用。
TypeScript 的关键字 static 有什么作用?
详情
static
关键字在 TypeScript 中用于定义类的静态成员。静态成员属于类本身,而不是类的实例。换句话说,你可以在不创建类的实例的情况下,直接通过类名来访问静态成员。这对需要共享数据或方法,而不依赖于特定对象实例的场景尤为有用。
TypeScript 类的 readonly 修饰符有什么作用?
详情
TypeScript 的 readonly 修饰符用于确保类的属性在初始化之后不可修改。它能够让我们在编译期间捕捉到对只读属性的修改,从而增强代码的稳定性和可维护性。
扩展知识
在 TypeScript 中,我们可以使用 readonly 修饰符来声明一个类的属性为只读,这样的属性一旦在构造函数中初始化之后,就不能再被修改了。具体使用方法如下:
1)定义类并使用 readonly 修饰符:
class Person {
readonly name: string;
constructor(name: string) {
this.name = name;
}
}
2)尝试修改只读属性:
const person = new Person("Alice");
person.name = "Bob"; // Error: Cannot assign to 'name' because it is a read-only property.
3)同样地,readonly 修饰符也可以用于接口和类型别名:
interface Car {
readonly brand: string;
}
const myCar: Car = { brand: "Toyota" };
myCar.brand = "Honda"; // Error: Cannot assign to 'brand' because it is a read-only property.
再进一步,如果我们需要定义一个类或者接口,其中有某些属性在对象创建后可以被重新赋值,而有些属性则永远不能被修改,这时 readonly 就会非常实用。它可以确保对象在整个生命周期中维持其不可变的部分不被改变,从而帮助我们避免意外错误。
此外,readonly 修饰符与 JavaScript 的 const 关键字有些类似,但存在一些重要区别:const 用于变量不可以重新赋值,而 readonly 用于属性不可以被修改。例如,const 只是对引用的限制,但是它引用的对象的属性依然可以修改:
const arr: number[] = [1, 2, 3];
arr[0] = 4; // This is allowed
arr = [4, 5, 6]; // Error: Cannot assign to 'arr' because it is a constant.
在比对的时候就能看出 readonly 的显著优势,特别是在维护数据完整性方面。许多高级编程实践,例如对不可变数据结构的需求,也会依赖 readonly 属性来保证对象的一部分不可变。
什么是 TypeScript 的 void 类型?
详情
TypeScript 的 void 类型与 JavaScript 中的 undefined 稍有不同。简单来说,void 类型通常表示一个函数没有返回值。当一个函数类型的返回值被声明为 void 时,意味着该函数要么不返回任何值,要么返回 undefined。
常见的例子就是在编写一个日志类函数时:
function logMessage(message: string): void {
console.log(message);
}
扩展知识
- 函数签名中的应用:
- void 类型最常见的应用场景就是在定义函数签名时。虽然 TypeScript 允许函数类型不显式声明返回类型,但明确标明返回类型为 void 有助于提高代码的可读性和可维护性。
- 差异于 any 和 unknown:
- void 与 any 和 unknown 都是 TypeScript 中的特殊类型。与 any 代表任何类型不同,void 特指没有返回值。unknown 表示未知类型,通常用在更严格的类型检查中。
- 变量类型不建议使用:
- 与函数返回值不同,通常不建议将 void 类型用作变量类型。因为它基本没有实际意义,且可能会混淆代码的可读性。
let unusable: void = undefined; // 可行,但不推荐
- 泛型和 void:
- 在泛型中,void 有时会被用作一种占位符。比如,你可能有一个函数类型接口,它返回任何类型,因此需要一个占位符来表示没有指定具体类型的返回值。
function genericFunction<T>(input: T): void {
console.log(input);
}
- 与 undefined 的区别:
- 虽然在实践中经常会看到函数返回 undefined 或者没有返回值,但在 TypeScript 中,void 与 undefined 是不同的类型。undefined 是一种实际的值类型,而 void 更像是一种类型约束,规定了函数不会去返回任何有意义的值。
什么是 TypeScript 的 object 类型?
详情
TypeScript 的 object 类型用于表示非原始类型,即不包括 number, string, boolean, symbol, null, 和 undefined 等基本类型的数据。简单来说,它主要是用来描述对象类型,这些对象可以是任意形式的、拥有任意属性和方法的 JavaScript 对象。
扩展知识
1)基本使用
当你想要声明一个变量类型为 object 时,你可以这样做:
let myObject: object;
myObject = { name: "Alice", age: 30 }; // 合法
myObject = [1, 2, 3]; // 合法
myObject = function() {}; // 合法
myObject = 'string'; // 非法,类型 'string' 不能赋值给类型 'object'
object 可以包含任何 JavaScript 的对象类型,但不包括原始类型的值。
2)与具体对象类型的区别
object 类型代表的对象类型是宽泛的,它允许任何形式的对象。若想更加精确地描述对象的结构和属性,你可以使用接口或类型别名。
interface Person {
name: string;
age: number;
}
let person: Person = {
name: "Alice",
age: 30
};
3)Object 与 object
在 TypeScript 中,Object(首字母大写)和 object(全小写)是不同的:
- Object 是一种顶级类型(Top Type),包含所有类型,包括基础类型。
- object 是一种非原始类型,即更严格的类型,不允许基本类型(number, string 等)。
let obj1: Object;
let obj2: object;
obj1 = 42; // 合法
obj2 = 42; // 非法
4)Object 和 {} 的差异
Object 和 {} 有时在 TypeScript 中也有一些细微的差距。 {} 表示任何类型的值(与 any 类似),但仍然进行些许类型检查,对于基本类型和对象类型皆可接受。
let obj3: {};
obj3 = 42; // 合法
obj3 = "text"; // 合法
obj3 = { name: "Alice" }; // 合法
let obj4: Object = 42; // 合法,因为 Object 可以是任何类型
5)高级类型操作
你还可以用 TypeScript 的 Record 泛型来创建具有特定键和值类型的对象类型。
let user: Record<string, number> = { "Alice": 30, "Bob": 25 };
6)类型断言
有时候,你可能需要将更精确的类型断言到 object,这可以通过类型断言来实现。
let data: object = {};
(data as { isUser: boolean }).isUser = true;
什么是 TypeScript 的 never 类型?
详情
TypeScript 的 never 类型表示那些永不存在的值。它是 TypeScript 中最严格的类型,通常用于表示函数永远不会返回值(要么抛出异常,要么无限循环)。当你确保某个变量永远不会触发其特定分支时,never 类型就显得尤为有用。
function error(message: string): never {
throw new Error(message);
}
function fail() {
return error("Something went wrong!");
}
function infiniteLoop(): never {
while (true) {}
}
扩展知识
这里我将进一步扩展说明 never 类型的使用场景以及它和 void 类型的区别。
1)使用场景
never 类型常见的使用场景包括以下几个:
- 抛出异常:当函数抛出异常时,它不会返回任何值,因此其返回类型为 never。
- 无限循环:当函数包含无限循环时,它也不会返回,因此返回类型是 never。
- 死代码检测:当 TypeScript 检测到某个代码分支不可能被执行时,它会推断出该分支的类型是 never。
2)never 和 void 的区别
void 和 never 都表示没有返回值的情况,但它们有本质区别:
- void 类型 表示没有任何类型,通常用于函数没有返回值的情况。例如,函数可以正常结束但不返回值。
- never 类型 表示不可能到达的终点,通常用于函数永远不会有返回值的情况。换句话说,函数会执行到抛出异常或者无限循环中。
void 和 never 在使用上的差别可以防止一些潜在的编程错误,提高代码的准确性和健壮性。
3)TypeScript 类型系统中的地位
never 是所有类型的子类型,可以赋值给任何类型,但反过来却不行。这个特性使得 never 类型特别适合错误处理和意外情况的处理。
什么是 TypeScript 的 Function 类型?
详情
在 TypeScript 中,Function 类型是一种用于对函数签名进行类型约束的类型标注。它可以帮助开发者在代码中明确指定函数的参数类型以及返回值类型,从而提升代码的可读性、可维护性以及类型安全性。
1. 函数类型的基本定义
可以使用函数类型字面量来定义函数的参数类型和返回值类型。
// 定义一个函数类型,接受两个 number 类型的参数,返回一个 number 类型的值
let add: (x: number, y: number) => number;
// 实现这个函数
add = function(x: number, y: number) {
return x + y;
};
// 调用函数
const result = add(1, 2);
console.log(result); // 输出 3
2. 可选参数和默认参数
在函数类型中,可以使用 ?
来标记可选参数,也可以为参数设置默认值。
// 定义一个函数类型,接受两个 number 类型的参数,返回一个 number 类型的值
let add: (x: number, y: number) => number;
// 实现这个函数
add = function(x: number, y: number) {
return x + y;
};
// 调用函数
const result = add(1, 2);
console.log(result); // 输出 3
3. 剩余参数
可以使用剩余参数来表示不确定数量的参数。
// 定义一个函数类型,接受任意数量的 number 类型的参数,返回一个 number 类型的值
let sum: (...numbers: number[]) => number;
// 实现这个函数
sum = function(...numbers: number[]) {
return numbers.reduce((acc, num) => acc + num, 0);
};
// 调用函数
console.log(sum(1, 2, 3)); // 输出 6
4. 函数重载
函数重载允许你为同一个函数提供多个不同的调用签名,以便根据不同的参数类型和数量来调用不同的实现。
// 函数重载签名
function add(a: number, b: number): number;
function add(a: string, b: string): string;
// 函数实现
function add(a: number | string, b: number | string): number | string {
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
} else if (typeof a === 'string' && typeof b === 'string') {
return a + b;
}
throw new Error('Invalid arguments');
}
// 调用函数
console.log(add(1, 2)); // 输出 3
console.log(add('Hello', ' World')); // 输出 Hello World
什么是 TypeScript 的 any 类型?
详情
TypeScript 的 any 类型是一个特殊的类型,用来表示可以是任何类型的值。当你不确定某个变量会是什么类型,或者你希望类型检查器不对某个变量进行类型检查时,可以使用 any 类型。
举个简单的例子,假设你有一个变量 x,你不知道它会是什么类型,你可以定义它为 any 类型:
let x: any;
x = 42; // OK
x = "Hello"; // OK
x = true; // OK
使用 any 类型可以暂时跳过 TypeScript 的类型检查,但是这也意味着你失去了类型系统带来的安全性和提示功能。
什么是 TypeScript 的 unknown 类型?和 any 类型有什么区别?
详情
TypeScript 的 unknown 类型表示我们不知道的值,它和 any 类型都能够表示所有值,但有本质区别。主要区别在于:使用 any 类型时,你可以对该类型的值进行任意操作,而不会有任何类型检查;而使用 unknown 类型时,必须在执行大多数操作前先进行类型检查。
也就是说,unknown 更加安全和严格,它要求你在使用值之前必须明确其类型。
扩展知识
1)为什么使用 unknown:
- 用 unknown 代替 any 可以更好地利用 TypeScript 的类型系统,使代码更加健壮。
- unknown 可以帮助我们明确数据流和类型检查,通过强制我们进行类型断言或检查减少错误。
2)代码示例:
let value: any;
value = 42;
console.log(value.toFixed(2)); // 42.00,没有报错
let unknownValue: unknown;
unknownValue = 42;
// console.log(unknownValue.toFixed(2)); // 报错:对象的类型为 "unknown"
if (typeof unknownValue === "number") {
console.log(unknownValue.toFixed(2)); // 42.00,只在类型为 number 时执行
}
3)使用场景:
- 接口的参数或返回值未知时优先使用 unknown,通过运行时的类型检查确保安全。
- 在一些通用函数中,可能需要处理不同类型的数据,通过 unknown 类型可以确保类型安全而不是盲目地使用 any。
4)与其他类型的比较:
- any: 最松散的类型,没有类型检查。
- unknown: 强制类型检查和类型断言,更加安全。
- never: 表示永不存在的值,可以作为永不返回或者抛出异常的函数返回类型。
- void: 用于函数无返回值的情况,与 unknown 和 any 无法直接比较。
5)实践中的常见错误:
- 使用 any 可能导致意想不到类型错误,因为缺少类型检查。
- 过度复杂的类型断言可能会使代码难以维护,需要权衡使用 unknown 带来的复杂性。
TypeScript 的关键字 extends 有什么作用?
详情
在 TypeScript 中,关键字 extends
主要用于表示类型继承和类型约束。
- 类型继承:用来实现类的继承。通过
extends
,一个类可以从另一个类继承属性和方法,让代码更加模块化和重用。例如:
- 类型继承:用来实现类的继承。通过
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
move(distance: number): void {
console.log(`${this.name} moved ${distance} meters.`);
}
}
class Dog extends Animal {
bark(): void {
console.log('Woof! Woof!');
}
}
const dog = new Dog('Lucky');
dog.bark(); // 输出:Woof! Woof!
dog.move(10); // 输出:Lucky moved 10 meters.
- 类型约束:用来约束泛型类型。通过
extends
,我们可以指定一个类型必须是另一个类型的子类型,从而确保类型的安全性。例如:
- 类型约束:用来约束泛型类型。通过
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength({ length: 10, value: 3 }); // 输出:10
扩展知识
了解了 extends
关键字的基本用法,我们再来深入探讨一些相关概念和高级特性。
接口继承接口:不仅是类可以继承,接口也可以通过 extends
继承其他接口。这使得接口的定义更加灵活和复用。
- 接口继承接口:不仅是类可以继承,接口也可以通过
extends
继承其他接口。这使得接口的定义更加灵活和复用。
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
const square: Square = {
color: 'blue',
sideLength: 10
};
- 混入类:通过组合多个类型来创建更灵活的代码,这称为
mixins
。虽然 TypeScript 没有直接支持多重继承,但可以使用mixin
模式来实现类似功能。
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}
class CanEat {
eat() {
console.log('Eating');
}
}
class CanWalk {
walk() {
console.log('Walking');
}
}
class Person implements CanEat, CanWalk {
eat: () => void;
walk: () => void;
}
applyMixins(Person, [CanEat, CanWalk]);
const person = new Person();
person.eat(); // 输出:Eating
person.walk(); // 输出:Walking
- 条件类型:TypeScript 提供了一种条件类型语法,通过
extends
实现条件分支,使得类型定义更有弹性和表达力。
type IsString<T> = T extends string ? 'yes' : 'no';
type T1 = IsString<string>; // 'yes'
type T2 = IsString<number>; // 'no'
TypeScript 的关键字 infer 有什么作用?
详情
TypeScript 中的关键字 infer
通常与条件类型(conditional types)一起使用,用于在类型检查中过度复杂或者未知的类型结构中进行类型推断。它可以从类型中“提取”类型变量,使得我们可以在条件类型的 extends 分支中对其进行操作。这是在处理复杂类型转换和类型推断时非常强大且有用的工具。
简而言之,infer
关键字的主要作用就是“推断类型变量”。
扩展知识
1)基本用法
- 条件类型通常写作
T extends U ? X : Y
,表示如果类型T
可以赋值给类型U
,则结果类型为X
,否则为Y
。 infer
与条件类型结合使用时,可以用于在extends
条件中进行模式匹配,并且可以提取出类型的一部分进行后续的使用。
举个简单的例子:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
这里 infer R
意思是,如果 T
是一个函数类型,那么从 T
中提取其返回值类型 R
。如果 T
不是函数类型,就返回 any
。
2)复杂用法
infer
关键字能处理复杂类型结构。比如,我们可以用它来获取数组元素的类型:
type ElementType<T> = T extends (infer U)[] ? U : T;
在这段代码中,如果 T 是一个数组类型(例如 number[]),那么 ElementType<number[]> 推断为 number
。
3)递归类型推断
- 在更复杂的泛型编程中,
infer
允许我们对类型进行递归推断。例如,从一个嵌套数组中推断出最内层的元素类型:
type DeepElementType<T> = T extends (infer U)[] ? DeepElementType<U> : T;
如果我们有类型 number[][]
,DeepElementType<number[][]>
将递归推断结果为 number
。
4)实战应用
- 在实际工作中,经常需要使用 infer 类型进行复杂类型计算,如提取复杂对象中的嵌套类型结构、综合多个条件类型简化复杂类型判断等。例如:
type FunctionArgumentType<T> = T extends (arg: infer A) => any ? A : never;
type ArgumentType = FunctionArgumentType<(x: string) => void>; // ArgumentType 将被推断为 string
5)类型安全和简化
使用 infer 可以提高代码的类型安全(type safety),辅助我们在复杂系统中创建更加细致而准确的类型定义,以及避免显式类型声明所带来的复杂和冗长。
什么是 TypeScript 的泛型?
详情
TypeScript 的泛型是一种让类型可以参数化的工具。它不仅仅局限于某个特定的类型,而是可以接受任意的类型参数。泛型的主要目的是增强代码的复用性,同时保持良好的类型安全性。
1. 泛型函数
泛型函数可以处理不同类型的数据,而不需要为每种数据类型编写特定的函数。通过在函数名后面使用 <>
来定义泛型类型参数。
// 定义一个泛型函数,接受一个参数并返回它
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数处理不同类型的数据
let output1 = identity<string>("myString"); // 指定泛型类型为 string
let output2 = identity(100); // 类型推断,自动推断泛型类型为 number
console.log(output1);
console.log(output2);
2. 泛型接口
泛型接口可以定义具有泛型类型参数的接口,使得接口可以处理不同类型的数据。
// 定义一个泛型接口
interface GenericIdentityFn<T> {
(arg: T): T;
}
// 使用泛型接口定义函数
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
console.log(myIdentity(10));
3. 泛型类
泛型类可以处理不同类型的数据,通过在类名后面使用 <>
来定义泛型类型参数。
// 定义一个泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
// 使用泛型类处理 number 类型的数据
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
console.log(myGenericNumber.add(5, 3));
4. 泛型约束
有时候你可能希望泛型类型参数满足某些条件,这时可以使用泛型约束。通过 extends
关键字来实现泛型约束。
// 定义一个接口,用于泛型约束
interface Lengthwise {
length: number;
}
// 定义一个泛型函数,使用泛型约束
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
// 可以传入具有 length 属性的对象
loggingIdentity("hello");
loggingIdentity([1, 2, 3]);
什么是 TypeScript 的索引访问类型?
详情
TypeScript 的索引访问类型(Index Access Types)是一种强大的类型工具,它允许你通过索引来访问其他类型的特定部分,从而创建新的类型。这种类型可以基于现有的类型,动态地提取其属性的类型。下面详细介绍索引访问类型:
基本语法
索引访问类型的基本语法是使用方括号 []
来指定要访问的属性名或索引。例如,对于一个对象类型,你可以通过属性名来访问该属性的类型;对于数组类型,你可以通过索引来访问数组元素的类型。
访问对象属性的类型
以下是一个通过索引访问类型来获取对象属性类型的示例:
// 定义一个对象类型
type Person = {
name: string;
age: number;
isStudent: boolean;
};
// 使用索引访问类型获取 name 属性的类型
type NameType = Person['name'];
// 使用索引访问类型获取 age 属性的类型
type AgeType = Person['age'];
// 验证类型
let nameValue: NameType = 'John';
let ageValue: AgeType = 30;
在上述代码中,Person['name']
表示 Person
类型中 name
属性的类型,即 string
;Person['age']
表示 Person
类型中 age
属性的类型,即 number
。
访问数组元素的类型
对于数组类型,你可以使用索引访问类型来获取数组元素的类型。通常使用 number
作为索引来访问数组元素的类型。
// 定义一个数组类型
type NumberArray = number[];
// 使用索引访问类型获取数组元素的类型
type ElementType = NumberArray[number];
// 验证类型
let element: ElementType = 10;
在这个例子中,NumberArray[number]
表示 NumberArray
数组中元素的类型,即 number
。
访问数组元素的类型
对于数组类型,你可以使用索引访问类型来获取数组元素的类型。通常使用 number
作为索引来访问数组元素的类型。
// 定义一个数组类型
type NumberArray = number[];
// 使用索引访问类型获取数组元素的类型
type ElementType = NumberArray[number];
// 验证类型
let element: ElementType = 10;
在这个例子中,NumberArray[number]
表示 NumberArray
数组中元素的类型,即 number
。
使用联合类型作为索引
你还可以使用联合类型作为索引来访问多个属性的类型。
// 定义一个对象类型
type Person = {
name: string;
age: number;
isStudent: boolean;
};
// 使用联合类型作为索引访问多个属性的类型
type NameOrAgeType = Person['name' | 'age'];
// 验证类型
let value1: NameOrAgeType = 'John';
let value2: NameOrAgeType = 30;
在上述代码中,Person['name' | 'age']
表示 Person
类型中 name
或 age
属性的类型,即 string | number
。
总结
索引访问类型是 TypeScript 中一种灵活且强大的类型工具,它允许你根据现有类型动态地创建新的类型。通过索引访问类型,你可以获取对象属性的类型、数组元素的类型,甚至可以使用联合类型作为索引来访问多个属性的类型。这种特性使得代码更加灵活和可维护。
什么是 TypeScript 的条件类型?
详情
TypeScript 的条件类型是一种在类型层面进行条件判断的机制,它允许根据某个类型条件来选择不同的类型。条件类型的语法类似于 JavaScript 中的三元运算符,其基本形式为 T extends U ? X : Y
,含义是:如果类型 T
可以赋值给类型 U
,则结果类型为 X
,否则为 Y
。
基本示例
// 定义一个条件类型
type IsString<T> = T extends string ? true : false;
// 使用条件类型
type Result1 = IsString<string>; // true
type Result2 = IsString<number>; // false
在上述代码中,IsString
是一个条件类型,它接收一个泛型类型参数 T
。如果 T
是 string
类型,那么 IsString<T>
的结果就是 true
类型;否则,结果为 false
类型。
条件类型的嵌套
条件类型可以嵌套使用,以实现更复杂的类型判断。
// 定义一个嵌套的条件类型
type CheckType<T> = T extends string
? 'It is a string'
: T extends number
? 'It is a number'
: 'It is another type';
// 使用嵌套条件类型
type Result3 = CheckType<string>; // 'It is a string'
type Result4 = CheckType<number>; // 'It is a number'
type Result5 = CheckType<boolean>; // 'It is another type'
这里的 CheckType
类型会根据传入的泛型参数 T
的类型,进行多层条件判断,最终返回相应的字符串类型。
在泛型中使用条件类型
条件类型在泛型函数或泛型类中非常有用,可以根据不同的类型参数来返回不同的类型。
// 定义一个泛型函数,使用条件类型
function processValue<T>(value: T): T extends string ? string[] : T {
if (typeof value === 'string') {
return value.split('') as any;
}
return value;
}
// 使用泛型函数
const result6 = processValue('hello'); // 返回 string[] 类型
const result7 = processValue(123); // 返回 number 类型
在这个例子中,processValue
函数根据传入的参数 value
的类型,使用条件类型来决定返回值的类型。如果 value
是 string
类型,函数会将字符串拆分成字符数组并返回;否则,直接返回原参数。
分布式条件类型
当条件类型作用于泛型类型时,如果泛型类型是一个联合类型,那么条件类型会自动对联合类型的每个成员进行分布式处理。
// 定义一个条件类型
type ToArray<T> = T extends any ? T[] : never;
// 使用分布式条件类型
type Result8 = ToArray<string | number>; // string[] | number[]
这里的 ToArray
类型会将联合类型 string | number
的每个成员都转换为对应的数组类型,最终结果是 string[] | number[]
。
综上所述,TypeScript 的条件类型为类型系统提供了强大的灵活性和表达能力,使得开发者可以在类型层面进行复杂的条件判断和类型转换。
什么是 TypeScript 的映射类型?
详情
TypeScript 的映射类型是一种高级类型,它允许我们根据已有类型生成新的类型。通过使用映射类型,可以将特定的变换应用到类型的属性上,生成一个新的类型。例如,假设我们有一个接口 Person,其中包含一些属性,现在我们想要创建一个新类型,将 Person 中所有属性都设为可选属性(Partial类型);或者我们想要创建一个新类型,使 Person 中所有属性变为只读(Readonly类型)。这些操作都可以通过映射类型来实现。
基本语法
映射类型的基本语法如下:
{ [P in K]: T }
- P 是一个类型变量,它会遍历 K 中的每个属性名。
- K 是一个联合类型,包含了所有要遍历的属性名。
- T 是一个类型表达式,它定义了每个属性的新类型。
示例
- 将一个类型的所有属性变为只读
type ReadonlyExample<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
name: string;
age: number;
}
type ReadonlyUser = ReadonlyExample<User>;
// 等同于
// interface ReadonlyUser {
// readonly name: string;
// readonly age: number;
// }
- 将一个类型的所有属性变为可选
type PartialExample<T> = {
[P in keyof T]?: T[P];
};
interface User {
name: string;
age: number;
}
type PartialUser = PartialExample<User>;
// 等同于
// interface PartialUser {
// name?: string;
// age?: number;
// }
- 对属性值的类型进行转换
type Stringify<T> = {
[P in keyof T]: string;
};
interface User {
name: string;
age: number;
}
type StringifiedUser = Stringify<User>;
// 等同于
// interface StringifiedUser {
// name: string;
// age: string;
// }
总结
映射类型是 TypeScript 中非常有用的特性,它可以帮助你根据现有的类型创建新的类型,从而提高代码的可维护性和复用性。通过使用映射类型,你可以对类型的属性进行批量转换,而不需要手动为每个属性编写新的类型定义。
什么是 TypeScript 的模板字面量类型?
详情
TypeScript 的模板字面量类型(Template Literal Types)是一种类型操作,它允许我们通过字符串模板字面量创建新的字符串类型。这是从 TypeScript 4.1 开始引入的特性,允许我们通过组合现有的字符串字面量类型来形成新的、更复杂的字符串类型。
你可以把模板字面量类型看作是 JavaScript 中字符串模板字面量(Template Literals)的类型版本。它能够动态地生成新的类型,基于现有的字符串类型进行匹配和替换。
基本语法
模板字面量类型的基本语法如下:
`prefix${Type}`
其中,prefix
是一个字符串字面量,${Type}
是一个类型变量,它可以是任何字符串字面量类型、联合类型或其他模板字面量类型。
示例
- 简单的模板字面量类型
type World = "world";
type Greeting = `hello ${World}`; // "hello world"
- 联合类型与模板字面量类型
type Direction = "left" | "right" | "up" | "down";
type MoveCommand = `move ${Direction}`; // "move left" | "move right" | "move up" | "move down"
- 多个类型变量的模板字面量类型
type VerticalDirection = "top" | "bottom";
type HorizontalDirection = "left" | "right";
type Corner = `${VerticalDirection}-${HorizontalDirection}`; // "top-left" | "top-right" | "bottom-left" | "bottom-right"
应用场景
- 生成事件名称
type EventName<T extends string> = `${T}Changed`;
type UserEvent = EventName<"user">; // "userChanged"
- 生成 CSS 类名
type Size = "small" | "medium" | "large";
type ButtonClass = `button-${Size}`; // "button-small" | "button-medium" | "button-large"
总结 模板字面量类型是 TypeScript 中非常有用的特性,它允许你在类型系统中使用字符串拼接的方式来创建新的字符串字面量类型。通过使用模板字面量类型,你可以更精确地定义类型,从而提高代码的可维护性和类型安全性。
TypeScript 的类型操作符 keyof 有什么作用?
详情
keyof 是 TypeScript 中非常重要的类型操作符。它的主要作用是用来获取某个对象类型的所有键(key)并生成一个联合类型。换句话说,keyof 操作符可以帮助我们从对象类型中提取出键的集合。
1. 获取对象类型的属性名联合类型
当 keyof
作用于一个对象类型时,它会返回该对象所有属性名组成的联合类型。例如:
interface Person {
name: string;
age: number;
isStudent: boolean;
}
type PersonKeys = keyof Person;
// PersonKeys 的类型为 'name' | 'age' | 'isStudent'
在这个例子中,keyof Person
返回了一个联合类型,包含了 Person
接口中所有属性的名称。
2. 结合泛型实现类型安全的属性访问
keyof
常与泛型一起使用,用于创建类型安全的函数,以确保在访问对象属性时不会出现类型错误。例如:
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const person: Person = {
name: 'Alice',
age: 25,
isStudent: false
};
const name = getProperty(person, 'name');
// 类型安全,只能传入 'name' | 'age' | 'isStudent' 中的一个
在这个 getProperty
函数中,泛型 K
被约束为 keyof T
,这意味着 key
参数必须是 obj
对象的合法属性名。
3. 用于类型保护和类型过滤
keyof
还可以用于类型保护和类型过滤,确保在代码中只操作对象的合法属性。例如:
function printProperty<T>(obj: T, key: keyof T) {
console.log(obj[key]);
}
const person: Person = {
name: 'Bob',
age: 30,
isStudent: true
};
printProperty(person, 'age');
// 类型安全,避免了访问不存在的属性
在这个 printProperty
函数中,key
参数被限制为 obj
对象的合法属性名,从而避免了运行时的属性不存在错误。
4. 在映射类型中使用
keyof
在映射类型中也非常有用,它可以用于遍历对象的所有属性并进行转换。例如:
type ReadonlyPerson = {
readonly [P in keyof Person]: Person[P];
};
// ReadonlyPerson 类型的属性都是只读的
在这个映射类型中,keyof Person
用于遍历 Person
接口的所有属性,并将它们转换为只读属性。
综上所述,keyof
操作符在 TypeScript 中提供了强大的类型操作能力,它使得我们可以在类型层面上操作对象的属性名,从而提高代码的类型安全性和可维护性。
TypeScript 的类型操作符 typeof 有什么作用?
详情
typeof 是 TypeScript 中的一个关键字,用来获取变量或表达式的类型。这在定义复杂类型和进行类型推断时非常有用。它有两种主要用途:
- 1)在运行时,用来获取一个值的类型
- 2)在静态类型检查时,用来获取一个已有变量的类型,主要用于类型推断
基本用法
typeof 操作符主要用于获取变量或者对象的类型。示例如下:
let num = 10;
type NumType = typeof num;
// NumType 类型等同于 number
let str = 'hello';
type StrType = typeof str;
// StrType 类型等同于 string
在上述代码中,typeof num
返回的类型是 number
,typeof str
返回的类型是 string
。
应用场景
- 获取函数的类型 可以使用 typeof 来获取函数的类型,进而实现类型的复用。示例如下:
function add(a: number, b: number) {
return a + b;
}
type AddFunction = typeof add;
// AddFunction 类型等同于 (a: number, b: number) => number
let anotherAdd: AddFunction = (x, y) => x + y;
在这个例子中,typeof add
返回了 add
函数的类型,我们可以用这个类型去定义其他具有相同签名的函数。
- 与
keyof
结合使用
- 与
typeof
可以和 keyof
结合,用于获取对象属性名的联合类型。示例如下:
const person = {
name: 'Alice',
age: 25
};
type PersonKeys = keyof typeof person;
// PersonKeys 类型等同于 'name' | 'age'
这里先使用 typeof person
获取 person
对象的类型,再用 keyof
获取该类型所有属性名组成的联合类型。
- 在模块中使用 在 TypeScript 模块里,
typeof
可以用来获取模块导出的类型。示例如下:
- 在模块中使用 在 TypeScript 模块里,
// utils.ts
export function multiply(a: number, b: number) {
return a * b;
}
// main.ts
import * as utils from './utils';
type MultiplyFunction = typeof utils.multiply;
// MultiplyFunction 类型等同于 (a: number, b: number) => number
在这个例子中,通过 typeof utils.multiply
获取了 multiply
函数的类型。
总结
typeof
类型操作符在 TypeScript 中是一个非常实用的工具,它允许我们在类型层面获取变量、对象、函数的类型,从而实现类型的复用和类型安全的代码编写。通过结合其他类型操作符,如 keyof
,还能进一步增强类型系统的表达能力。
TypeScript 的内置工具类型 Awaited 有什么作用?
详情
在 TypeScript 中,Awaited
是一个内置的工具类型,它主要用于处理异步操作和 Promise
类型。其核心作用是获取一个 Promise
所包裹的值的类型,或者递归地解包嵌套的 Promise
类型,就像在 async/await
语法中使用 await
关键字来获取 Promise
的解析值一样。
基本语法
type Awaited<T> = T extends null | undefined ? T :
T extends object & { then(onfulfilled: infer F): any } ?
(F extends ((value: infer V, ...args: any[]) => any) ? Awaited<V> : never) :
T;
具体作用和示例
- 解包单层
Promise
- 解包单层
当你有一个 Promise
类型时,Awaited 可以帮助你获取该 Promise
最终解析的值的类型。
// 定义一个返回 Promise<number> 的函数
function fetchNumber(): Promise<number> {
return Promise.resolve(42);
}
// 使用 Awaited 来获取 Promise 解析值的类型
type Result = Awaited<ReturnType<typeof fetchNumber>>;
// Result 类型为 number
- 解包多层嵌套的
Promise
- 解包多层嵌套的
Awaited
可以递归地解包嵌套的 Promise
类型,直到获取到最内层非 Promise
值的类型。
// 定义一个返回 Promise<Promise<string>> 的函数
function fetchNestedPromise(): Promise<Promise<string>> {
return Promise.resolve(Promise.resolve('Hello, TypeScript!'));
}
// 使用 Awaited 来获取嵌套 Promise 最终解析值的类型
type NestedResult = Awaited<ReturnType<typeof fetchNestedPromise>>;
// NestedResult 类型为 string
总结
Awaited 工具类型在处理异步操作和 Promise 类型时非常有用,它允许你在类型层面上模拟 await 操作,从而更准确地定义和处理异步操作的结果类型,提高代码的类型安全性和可维护性。
TypeScript 的内置工具类型 Partial 有什么作用?
详情
在 TypeScript 里,Partial<T>
是一个内置的工具类型,它的作用是将一个类型 T
的所有属性变为可选属性。这意味着对于使用 Partial<T>
创建的新类型,其对象实例可以只包含原类型 T
的部分属性,或者不包含任何属性。
基本语法
type Partial<T> = {
[P in keyof T]?: T[P];
};
这里使用了映射类型,keyof T
会获取类型 T
的所有属性名,[P in keyof T]
会遍历这些属性名,?:
表示将这些属性设置为可选属性,T[P]
则表示属性的类型保持不变。
示例
// 定义一个 User 接口
interface User {
name: string;
age: number;
email: string;
}
// 使用 Partial 创建一个新类型
type PartialUser = Partial<User>;
// 创建 PartialUser 类型的对象实例
const partialUser: PartialUser = {
name: 'Alice'
// 可以只提供部分属性,或者不提供任何属性
};
在这个例子中,PartialUser
类型的对象可以只包含 User
接口的部分属性,比如只提供 name
属性,而 age
和 email
属性可以不提供。
应用场景
- 函数参数可选化:当你希望函数的参数可以只传入部分属性时,可以使用
Partial
来定义参数类型。
function updateUser(user: Partial<User>) {
// 处理用户信息更新
}
updateUser({ age: 25 });
// 可以只传入部分属性进行更新
- 对象合并:在合并对象时,如果希望某些属性是可选的,可以使用 Partial 来处理。
TypeScript 的内置工具类型 Required 有什么作用?
详情
在 TypeScript
里,Required<T>
是一个内置工具类型,它的主要作用是将类型 T
的所有可选属性转换为必需属性。也就是说,对于使用 Required<T>
创建的新类型,其对象实例必须包含原类型 T
的所有属性。
基本语法
type Required<T> = {
[P in keyof T]-?: T[P];
};
这里使用了映射类型,keyof T
会获取类型 T
的所有属性名,[P in keyof T]
会遍历这些属性名,-?
表示移除属性的可选标记(?
),从而将可选属性变为必需属性,T[P]
表示属性的类型保持不变。
示例
// 定义一个包含可选属性的接口
interface User {
name?: string;
age?: number;
email?: string;
}
// 使用 Required 创建一个新类型
type RequiredUser = Required<User>;
// 创建 RequiredUser 类型的对象实例
const requiredUser: RequiredUser = {
name: 'Alice',
age: 25,
email: 'alice@example.com'
// 必须提供所有属性
};
在这个例子中,RequiredUser
类型的对象必须包含 User
接口定义的所有属性,因为 Required
工具类型将 User
中的可选属性都转换为了必需属性。
应用场景
- 强制完整性:当你需要确保对象包含所有特定属性时,可以使用 Required 来保证对象的完整性。
function registerUser(user: Required<User>) {
// 处理用户注册逻辑
}
registerUser({ name: 'Bob', age: 30, email: 'bob@example.com' });
// 必须提供所有属性才能调用函数
- 类型修正:当你从一个可能包含可选属性的类型中创建一个需要完整属性的新类型时,
Required
很有用。
TypeScript 的内置工具类型 Readonly 有什么作用?
详情
在 TypeScript 中,Readonly<T>
是一个内置工具类型,它的主要作用是将类型 T
的所有属性转换为只读属性。一旦一个对象被定义为 Readonly<T>
类型,那么该对象的属性值在初始化之后就不能再被修改,这有助于增强代码的安全性和可维护性。
基本语法
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
这里使用了映射类型,keyof T
会获取类型 T
的所有属性名,[P in keyof T]
会遍历这些属性名,readonly
关键字将这些属性标记为只读属性,T[P]
表示属性的类型保持不变。
示例
// 定义一个 User 接口
interface User {
name: string;
age: number;
}
// 使用 Readonly 创建一个新类型
type ReadonlyUser = Readonly<User>;
// 创建 ReadonlyUser 类型的对象实例
const readonlyUser: ReadonlyUser = {
name: 'Alice',
age: 25
};
// 尝试修改只读属性会导致编译错误
// readonlyUser.name = 'Bob';
// 错误:无法分配到 "name" ,因为它是只读属性。
在这个例子中,ReadonlyUser
类型的对象 readonlyUser
的属性是只读的,任何修改属性值的操作都会在编译时抛出错误。
应用场景
- 保护数据:当你希望某个对象的属性值在初始化之后不被修改时,可以使用
Readonly
来确保数据的安全性。
function getImmutableUser(): Readonly<User> {
return {
name: 'Charlie',
age: 30
};
}
const immutableUser = getImmutableUser();
// 不能修改 immutableUser 的属性
- 函数参数保护:当函数接收一个对象作为参数,并且不希望函数内部修改该对象的属性时,可以使用
Readonly
来定义参数类型。
function printUser(user: Readonly<User>) {
console.log(`Name: ${user.name}, Age: ${user.age}`);
// 不能在函数内部修改 user 的属性
}
const user: User = { name: 'David', age: 35 };
printUser(user);
TypeScript 的内置工具类型 Pick 有什么作用?
详情
在 TypeScript 中,Pick<T, K>
是一个内置工具类型,它的作用是从类型 T
中选取一组由 K
指定的属性,从而创建一个新的类型。简单来说,Pick 可以让你从一个已有的类型中挑选出你需要的部分属性,形成一个新的类型。
基本语法
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
T
是原始类型,即你要从中选取属性的类型。K
是一个联合类型,它必须是T
的属性名组成的联合类型(通过keyof T
来约束)。[P in K]
表示遍历K
中的每个属性名。T[P]
表示新类型中属性P
的类型与原始类型T
中属性P
的类型相同。
示例
// 定义一个 User 接口
interface User {
name: string;
age: number;
email: string;
phone: string;
}
// 使用 Pick 选取部分属性创建新类型
type UserInfo = Pick<User, 'name' | 'age'>;
// 创建 UserInfo 类型的对象实例
const userInfo: UserInfo = {
name: 'Alice',
age: 25
};
在这个例子中,UserInfo
类型是从 User
类型中选取了 name
和 age
属性创建的新类型。因此,userInfo
对象只需要包含 name
和 age
属性。
应用场景
- 数据筛选:当你只需要对象的部分属性时,可以使用 Pick 来创建一个只包含这些属性的新类型。
function displayUserInfo(user: Pick<User, 'name' | 'email'>) {
console.log(`Name: ${user.name}, Email: ${user.email}`);
}
const user: User = {
name: 'Bob',
age: 30,
email: 'bob@example.com',
phone: '123-456-7890'
};
displayUserInfo(user);
在这个例子中,displayUserInfo
函数只需要 User
对象的 name
和 email
属性,因此使用 Pick
来创建一个只包含这两个属性的新类型作为函数参数类型。
TypeScript 的内置工具类型 Record 有什么作用?
详情
在 TypeScript 里,Record<K, T>
是一个内置工具类型,它用于创建一个新的对象类型。这个新类型的属性名是由类型 K
所指定的,而属性值的类型则是由类型 T
来确定。
基本语法
type Record<K extends keyof any, T> = {
[P in K]: T;
};
K
:这是一个联合类型,它规定了新对象类型的属性名。K
必须是keyof any
的子类型,一般来说,K
可以是字符串字面量类型、数字字面量类型或者枚举类型。T
:这个类型定义了新对象类型中所有属性的值的类型。[P in K]
:通过映射类型,对K
中的每个属性名进行遍历。T
:表示每个属性的值的类型都是T
。
示例
// 创建一个属性名为 'a'、'b'、'c',属性值类型为 number 的对象类型
type NumberRecord = Record<'a' | 'b' | 'c', number>;
// 创建 NumberRecord 类型的对象实例
const numRecord: NumberRecord = {
a: 1,
b: 2,
c: 3
};
在这个例子中,NumberRecord
类型的对象必须包含 'a'
、'b'
、'c'
这三个属性,并且每个属性的值的类型都为 number
。
应用场景
- 批量创建对象:当你需要创建多个具有相同值类型的属性的对象时,
Record
类型就非常有用。
// 定义一个枚举类型
enum Fruit {
Apple = 'apple',
Banana = 'banana',
Orange = 'orange'
}
// 创建一个属性名为 Fruit 枚举成员,属性值类型为 string 的对象类型
type FruitDescription = Record<Fruit, string>;
// 创建 FruitDescription 类型的对象实例
const fruitDescriptions: FruitDescription = {
[Fruit.Apple]: 'A red or green fruit',
[Fruit.Banana]: 'A yellow fruit',
[Fruit.Orange]: 'A orange-colored fruit'
};
在这个例子中,FruitDescription
类型的对象包含了 Fruit
枚举中每个成员作为属性名,并且每个属性的值都是 string
类型。
TypeScript 的内置工具类型 Exclude 有什么作用?
详情
在 TypeScript 里,Exclude<T, U>
是一个内置工具类型,它的作用是从类型 T
中排除那些可以赋值给类型 U
的类型,从而创建一个新的类型。简单来讲,就是从一个联合类型里去除一部分特定的类型。
基本语法
type Exclude<T, U> = T extends U ? never : T;
T
:这是一个联合类型,也就是你要从中排除某些类型的原始类型。U
:同样是一个联合类型,代表你想要从T
中排除掉的类型。T extends U ? never : T
:这是一个条件类型。如果T
中的某个类型可以赋值给U
,那么该类型就会被替换为never
;反之,该类型会保留在新类型中。
示例
// 定义一个联合类型
type Colors = 'red' | 'green' | 'blue' | 'yellow';
// 定义要排除的类型
type PrimaryColors = 'red' | 'blue' | 'yellow';
// 使用 Exclude 创建新类型
type SecondaryColors = Exclude<Colors, PrimaryColors>;
// SecondaryColors 类型等同于 'green'
在这个例子中,Exclude<Colors, PrimaryColors>
从 Colors
联合类型里排除了 PrimaryColors
联合类型中的类型,最终得到的 SecondaryColors
类型就只剩下 'green'
了。
应用场景
- 类型过滤:当你需要从一个联合类型里移除某些特定类型时,
Exclude
非常有用。
type AllDataTypes = string | number | boolean | null | undefined;
type ValidDataTypes = Exclude<AllDataTypes, null | undefined>;
// ValidDataTypes 类型等同于 string | number | boolean
在这个例子中,Exclude<AllDataTypes, null | undefined>
从 AllDataTypes
联合类型中排除了 null
和 undefined
类型,得到了只包含有效数据类型的 ValidDataTypes
类型。
TypeScript 的内置工具类型 Extract 有什么作用?
详情
Extract<T, U>
是 TypeScript 中的一个内置工具类型,它的作用是从类型 T
中提取出可以赋值给类型 U
的那些类型,从而生成一个新的类型。简单来说,它能从一个联合类型里筛选出符合特定条件的类型。
基本语法
type Extract<T, U> = T extends U ? T : never;
这里使用了条件类型。如果 T
中的某个类型能赋值给 U
,那么这个类型会被保留在新类型中;反之,就会被替换为 never
类型。
示例
// 定义一个联合类型
type AllColors = 'red' | 'green' | 'blue' | 'yellow' | 'orange';
// 定义要提取的类型
type PrimaryColors = 'red' | 'blue' | 'yellow';
// 使用 Extract 提取类型
type ExtractedPrimaryColors = Extract<AllColors, PrimaryColors>;
// ExtractedPrimaryColors 类型为 'red' | 'blue' | 'yellow'
在这个例子里,Extract<AllColors, PrimaryColors>
从 AllColors
这个联合类型中,把能赋值给 PrimaryColors
的类型提取出来,得到了 ExtractedPrimaryColors
类型。
应用场景
- 筛选类型:当你有一个包含多种类型的联合类型,而你只需要其中一部分符合特定条件的类型时,就可以使用
Extract
。
// 定义一个包含不同数据类型的联合类型
type DataTypes = string | number | boolean | null | undefined;
// 定义原始数据类型
type PrimitiveTypes = string | number | boolean;
// 提取原始数据类型
type ExtractedPrimitiveTypes = Extract<DataTypes, PrimitiveTypes>;
// ExtractedPrimitiveTypes 类型为 string | number | boolean
在这个例子中,从 DataTypes
联合类型里提取出了 PrimitiveTypes
类型,也就是原始数据类型。
TypeScript 的内置工具类型 Omit 有什么作用?
详情
在 TypeScript 中,Omit<T, K>
是一个内置工具类型,它的作用是从类型 T
中移除由 K
指定的属性,从而创建一个新的类型。这与 Pick
工具类型相反,Pick
是选取指定的属性,而 Omit
是排除指定的属性。
基本语法
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
T
是原始类型,即你要从中移除属性的类型。K
是一个联合类型,它包含了你想要从T
中移除的属性名。Exclude<keyof T, K>
会从T
的所有属性名中排除 K 中的属性名。Pick<T, Exclude<keyof T, K>>
会选取T
中剩余的属性,从而创建一个新的类型。
示例
// 定义一个 User 接口
interface User {
name: string;
age: number;
email: string;
phone: string;
}
// 使用 Omit 移除部分属性创建新类型
type UserWithoutContact = Omit<User, 'email' | 'phone'>;
// 创建 UserWithoutContact 类型的对象实例
const userWithoutContact: UserWithoutContact = {
name: 'Alice',
age: 25
};
在这个例子中,UserWithoutContact
类型是从 User
类型中移除了 email
和 phone
属性后创建的新类型。因此,userWithoutContact
对象只需要包含 name
和 age
属性。
应用场景
- 数据筛选:当你只需要对象的部分属性,而不需要其他属性时,可以使用
Omit
来创建一个只包含所需属性的新类型。
function displayUserBasicInfo(user: Omit<User, 'email' | 'phone'>) {
console.log(`Name: ${user.name}, Age: ${user.age}`);
}
const user: User = {
name: 'Bob',
age: 30,
email: 'bob@example.com',
phone: '123-456-7890'
};
displayUserBasicInfo(user);
在这个例子中,displayUserBasicInfo
函数只需要 User
对象的 name
和 age
属性,因此使用 Omit
来创建一个只包含这两个属性的新类型作为函数参数类型。
TypeScript 的内置工具类型 NonNullable 有什么作用?
详情
NonNullable
是 TypeScript 中的一个内置工具类型,它的主要作用是从一个类型中排除 null
和 undefined
类型。这个工具类型可以帮助你创建更严格的类型约束,确保在使用某个类型时不会出现 null
或 undefined
值。
基本语法
type NonNullable<T> = T extends null | undefined ? never : T;
- T:要处理的类型。
- 返回值: 返回一个新的类型,该类型是 T 中排除 null 和 undefined 后的类型。
示例
// 定义一个可能包含 null 或 undefined 的类型
type MaybeString = string | null | undefined;
// 使用 NonNullable 工具类型排除 null 和 undefined
type DefiniteString = NonNullable<MaybeString>;
// 现在 DefiniteString 只能是 string 类型
let str: DefiniteString = "hello"; // 合法
// let str: DefiniteString = null; // 错误,不能为 null
// let str: DefiniteString = undefined; // 错误,不能为 undefined
应用场景
在处理函数参数或返回值时,如果你希望确保某个值不会是 null
或 undefined
,可以使用 NonNullable
工具类型来创建更严格的类型约束。
function printLength(str: NonNullable<string | null | undefined>) {
console.log(str.length);
}
printLength("hello"); // 输出: 5
// printLength(null); // 错误,不能为 null
// printLength(undefined); // 错误,不能为 undefined
TypeScript 的内置工具类型 Parameters 有什么作用?
详情
Parameters
是 TypeScript 中的一个内置工具类型,它的主要作用是从一个函数类型中提取其参数类型,并将这些参数类型组成一个元组类型。这个工具类型可以帮助你在编写代码时,动态地获取函数的参数类型,而不需要手动重复定义这些类型。
基本语法
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any? P : never;
T
:要处理的函数类型。- 返回值: 返回一个元组类型,该元组类型包含了函数 T 的所有参数类型。
示例
// 定义一个函数类型
type MyFunction = (name: string, age: number) => void;
// 使用 Parameters 工具类型提取函数的参数类型
type MyFunctionParameters = Parameters<MyFunction>;
// MyFunctionParameters 现在是一个元组类型,包含两个元素:string 和 number
let args: MyFunctionParameters = ["John", 30]; // 合法
应用场景
在编写高阶函数或泛型函数时,Parameters
工具类型非常有用。例如,你可以使用它来创建一个函数,该函数接受另一个函数及其参数,并调用该函数:
function callFunction<T extends (...args: any) => any>(fn: T, ...args: Parameters<T>): ReturnType<T> {
return fn(...args);
}
function greet(name: string, age: number) {
return `Hello, ${name}! You are ${age} years old.`;
}
let result = callFunction(greet, "John", 30);
console.log(result); // 输出: Hello, John! You are 30 years old.
通过使用 Parameters
工具类型,你可以在编译时确保传递给 callFunction
的参数类型与 greet
函数的参数类型相匹配,从而提高代码的类型安全性。
TypeScript 的内置工具类型 ConstructorParameters 有什么作用?
详情
ConstructorParameters
是 TypeScript 里的一个内置工具类型,其主要用途是从构造函数类型中提取参数类型,并且将这些参数类型组合成一个元组类型。借助这个工具类型,在编写代码时能够动态获取构造函数的参数类型,而无需手动重新定义这些类型。
基本语法
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any? P : never;
T
:要处理的构造函数类型。- 返回值: 返回一个元组类型,该元组类型包含了构造函数 T 的所有参数类型。
示例
// 定义一个类
class Person {
constructor(name: string, age: number) {
// 构造函数逻辑
}
}
// 使用 ConstructorParameters 工具类型提取构造函数的参数类型
type PersonConstructorParameters = ConstructorParameters<typeof Person>;
// PersonConstructorParameters 现在是一个元组类型,包含两个元素:string 和 number
let args: PersonConstructorParameters = ["John", 30]; // 合法
应用场景
在编写高阶函数或者泛型函数时,ConstructorParameters
工具类型会很有用。例如,你可以创建一个函数,该函数接受一个构造函数及其参数,然后使用这些参数来创建一个新的实例:
function createInstance<T extends abstract new (...args: any) => any>(
Constructor: T,
...args: ConstructorParameters<T>
): InstanceType<T> {
return new Constructor(...args);
}
class Animal {
constructor(name: string) {
this.name = name;
}
name: string;
}
let dog = createInstance(Animal, "Buddy");
console.log(dog.name); // 输出: Buddy
通过使用 ConstructorParameters
工具类型,能在编译时确保传递给 createInstance
函数的参数类型与构造函数的参数类型相匹配,进而提高代码的类型安全性。
TypeScript 的内置工具类型 ReturnType 有什么作用?
详情
ReturnType
是 TypeScript 中的一个内置工具类型,它的主要作用是从一个函数类型中提取其返回值类型。这在需要动态获取函数返回值类型,而不想手动重复定义这些类型的场景中非常有用。
基本语法
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R? R : any;
T
:要处理的函数类型。- 返回值: 返回一个类型,该类型是函数 T 的返回值类型。
示例
// 定义一个函数类型
type AddFunction = (a: number, b: number) => number;
// 使用 ReturnType 工具类型提取函数的返回值类型
type AddResult = ReturnType<AddFunction>;
// AddResult 现在是 number 类型
let result: AddResult = 5; // 合法
应用场景
在编写高阶函数或泛型函数时,ReturnType
工具类型非常有用。例如,你可以使用它来创建一个函数,该函数接受另一个函数并返回其返回值类型:
function getFunctionReturnType<T extends (...args: any) => any>(fn: T): ReturnType<T> {
// 这里只是示例,实际中可能需要更复杂的逻辑
return undefined as any;
}
function multiply(a: number, b: number): number {
return a * b;
}
let returnType = getFunctionReturnType(multiply);
// returnType 的类型现在是 number
通过使用 ReturnType
工具类型,你可以在编译时确保代码与函数的返回值类型保持一致,从而提高代码的类型安全性。
TypeScript 的内置工具类型 InstanceType 有什么作用?
详情
InstanceType
是 TypeScript 中的一个内置工具类型,它的主要作用是从构造函数类型中提取实例类型。当你需要动态获取某个构造函数所创建的实例的类型时,InstanceType
就非常有用,避免了手动重复定义实例类型。
基本语法
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R? R : any;
T
:要处理的构造函数类型。- 返回值: 返回一个类型,该类型是构造函数 T 所创建的实例的类型。
示例
// 定义一个类
class User {
constructor(public name: string, public age: number) {}
getInfo() {
return `Name: ${this.name}, Age: ${this.age}`;
}
}
// 使用 InstanceType 工具类型提取 User 类的实例类型
type UserInstance = InstanceType<typeof User>;
// UserInstance 现在是 User 类的实例类型
let user: UserInstance = new User('Alice', 25);
console.log(user.getInfo()); // 输出: Name: Alice, Age: 25
应用场景
在编写泛型函数或者高阶函数时,InstanceType
工具类型能发挥重要作用。例如,你可以创建一个函数,它接受一个构造函数,然后返回该构造函数所创建实例的类型:
function createInstance<T extends abstract new (...args: any) => any>(Constructor: T): InstanceType<T> {
return new Constructor();
}
class Product {
constructor(public name: string, public price: number) {}
getDetails() {
return `Product: ${this.name}, Price: $${this.price}`;
}
}
let product = createInstance(Product);
console.log(product.getDetails()); // 输出: Product: [name], Price: $[price]
TypeScript 的内置工具类型 NoInfer 有什么作用?
详情
在 TypeScript 里,内置工具类型 NoInfer 是一种类型操纵技巧,它主要用于防止 TypeScript 的类型系统自动推断某些类型。具体来说,对于某些复杂的类型推断场景,我们可能希望手动指定某个类型而不让 TypeScript 自动推断,这时我们可以使用 NoInfer 来提醒编译器不要对该类型进行推断。
一个常见的使用场景是当我们希望类型参数在上下文中保持一致时,可以使用 NoInfer 来避免意外的类型更改。
扩展知识
1)NoInfer 实现原理:
NoInfer 类型通常是通过以下代码实现的,它使用条件类型和一个特殊的传入边界条件来使类型推断不起作用:
type NoInfer<T> = T & { [K in keyof T]: T[K] };
2)使用场景示例:
假设我们有一个函数 foo,它接受一个泛型类型 T 参数并且要求传入的参数精确匹配类型 T。在没有 NoInfer 的情况下,类型 T 可能会被推断成一个更宽泛的类型,从而导致不一致。
function foo<T>(arg: T): T {
return arg;
}
const a = { name: "Alice" };
const b = foo(a); // 在这里,TypeScript 可能会推断出一个宽泛的类型
如果我们希望传入参数精确匹配类型 T
,可以使用 NoInfer
:
function foo<T>(arg: NoInfer<T>): T {
return arg;
}
const a = { name: "Alice" };
const b = foo<{ name: string }>(a); // 类型必须完全匹配
3)替代方案和比较:
有时我们可以通过多种方法来控制类型推断。例如,可以通过显式类型注释、联合类型或使用其他条件类型来实现类似效果。每种方法都有其特定场景的适用性。
4)应用范围:
NoInfer
类型非常适合复杂的泛型类型定义、类型系统调试以及第三方库封装时需要确保类型安全的场合。在大型项目中,准确控制类型推断有助于增强代码的鲁棒性和可维护性。但需要注意的是,过度使用 NoInfer
可能会使类型定义难以理解,并且需要平衡代码的可维护性和类型系统的严格性。
TypeScript 的内置工具类型 ThisParameterType 有什么作用?
详情
ThisParameterType
是 TypeScript 中的一个内置工具类型,它的主要作用是从函数类型中提取 this
参数的类型。在 TypeScript 里,函数可以显式地指定 this
参数的类型,而 ThisParameterType
能够帮助我们获取这个类型。
基本语法
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any? U : unknown;
T
:要处理的函数类型。- 返回值: 如果函数类型
T
显式指定了this
参数的类型,那么返回该类型;如果没有显式指定,返回unknown
。
示例
// 定义一个显式指定 this 参数类型的函数
interface MyObject {
name: string;
}
function printName(this: MyObject) {
console.log(this.name);
}
// 使用 ThisParameterType 工具类型提取 this 参数的类型
type ThisType = ThisParameterType<typeof printName>;
// ThisType 现在是 MyObject 类型
let obj: ThisType = { name: 'Alice' };
printName.call(obj); // 输出: Alice
应用场景
在编写高阶函数或者泛型函数时,ThisParameterType
工具类型非常有用。例如,你可以创建一个函数,该函数接受另一个函数并返回其 this
参数的类型:
function getThisType<T extends (this: any, ...args: any[]) => any>(fn: T): ThisParameterType<T> {
// 这里只是示例,实际中可能需要更复杂的逻辑
return undefined as any;
}
function greet(this: { message: string }) {
console.log(this.message);
}
let thisType = getThisType(greet);
// thisType 的类型现在是 { message: string }
TypeScript 的内置工具类型 OmitThisParameter 有什么作用?
详情
OmitThisParameter 是 TypeScript 中的一个内置工具类型,它的主要作用是从函数类型中移除 this 参数,从而得到一个不包含 this 参数的新函数类型。
基本语法
type OmitThisParameter<T> = unknown extends ThisParameterType<T>? T : T extends (...args: infer A) => infer R? (...args: A) => R : T;
T
:要处理的函数类型。- 返回值: 返回一个新的函数类型,该类型不包含
this
参数。
示例
// 定义一个带有 this 参数的函数类型
interface Person {
name: string;
}
function greet(this: Person, message: string) {
return `${this.name}: ${message}`;
}
// 使用 OmitThisParameter 移除 this 参数
type GreetWithoutThis = OmitThisParameter<typeof greet>;
// GreetWithoutThis 现在是一个不包含 this 参数的函数类型
const greetFunction: GreetWithoutThis = greet.bind({ name: 'Alice' });
console.log(greetFunction('Hello!')); // 输出: Alice: Hello!
应用场景
在某些场景下,你可能需要将一个带有 this
参数的函数传递给一个不期望 this
参数的上下文,或者在编写高阶函数时,需要忽略传入函数的 this
参数。这时,OmitThisParameter
就非常有用。
// 定义一个高阶函数,接受一个不包含 this 参数的函数
function callFunction<T extends (...args: any[]) => any>(fn: T, ...args: Parameters<T>) {
return fn(...args);
}
// 使用 OmitThisParameter 处理带有 this 参数的函数
const newGreet = OmitThisParameter<typeof greet>;
const boundGreet = greet.bind({ name: 'Bob' });
const result = callFunction(newGreet, boundGreet, 'Hi!');
console.log(result); // 输出: Bob: Hi!
TypeScript 的内置工具类型 ThisType 有什么作用?
详情
ThisType
是 TypeScript 中的一个内置工具类型,它主要用于在对象字面量中控制 this
的类型。在普通的 JavaScript 和 TypeScript 代码里,this
的类型通常是由函数的调用方式决定的,但在某些复杂场景下,我们可能希望明确指定 this
的类型,这时就可以使用 ThisType 工具类型。
基本语法
ThisType
是一个泛型工具类型,它接收一个类型参数,用来指定 this
的类型。不过,ThisType
本身并不会返回一个具体的类型,而是用于修改对象字面量中方法里 this
的类型。
示例
下面通过一个示例来展示 ThisType
的使用:
// 引入 ThisType 工具类型
import { ThisType } from 'typescript';
// 定义一个接口,描述对象的结构
interface PersonMethods {
sayHello(): void;
setName(name: string): void;
}
// 定义一个接口,描述 this 的类型
interface PersonThis {
name: string;
}
// 使用 ThisType 来指定对象字面量中 this 的类型
const person: PersonMethods & ThisType<PersonThis> = {
sayHello() {
console.log(`Hello, my name is ${this.name}`);
},
setName(name) {
this.name = name;
}
};
// 使用 person 对象
person.setName('Alice');
person.sayHello(); // 输出: Hello, my name is Alice
注意事项
ThisType
只能在--noImplicitThis
选项开启的情况下使用。ThisType
通常用于对象字面量中,不能直接用于类或函数。
什么情况下 this 会引用不正确的类型?
详情
在 JavaScript 和 TypeScript 中,this
的指向是动态的,它的值取决于函数的调用方式,这可能会导致 this
引用不正确的类型,以下是几种常见的情况:
1. 函数作为回调函数传递时
当函数作为回调函数传递给其他函数时,this
的指向可能会发生变化。例如:
class MyClass {
value = 42;
printValue() {
console.log(this.value);
}
}
const myObject = new MyClass();
// 把 printValue 作为回调函数传递给 setTimeout
setTimeout(myObject.printValue, 1000);
箭头函数中的 this
箭头函数没有自己的 this
,它会捕获其所在上下文的 this
值。如果在错误的上下文中使用箭头函数,可能会导致 this
指向不符合预期。例如:
class MyClass {
value = 42;
getPrinter() {
return () => {
console.log(this.value);
};
}
}
const myObject = new MyClass();
const printer = myObject.getPrinter();
const anotherObject = { value: 100 };
// 尝试使用另一个对象调用 printer,但 this 仍然指向 myObject
printer.call(anotherObject);
3. 事件处理函数
在事件处理函数中,this
的指向通常是触发事件的元素,而不是你期望的对象。例如:
class MyComponent {
constructor() {
const button = document.createElement('button');
button.textContent = 'Click me';
button.addEventListener('click', this.handleClick);
document.body.appendChild(button);
}
handleClick() {
// 这里的 this 指向 button 元素,而不是 MyComponent 实例
console.log(this);
}
}
const component = new MyComponent();
为什么要使用 TypeScript?TypeScript 有哪些常用特性?
详情
为什么使用 TypeScript?
TypeScript 是 JavaScript 的超集,它为 JavaScript 添加了 静态类型检查 和更多现代语言特性(比如箭头函数、解构赋值、模块化等),再配合 WebStorm、VSCode 等主流 IDE,可以实现智能提示、代码补全、自动重构等功能,使得代码更加健壮、可维护性更高。
TypeScript 的常用特性
TypeScript 的特性非常多,在官方文档可以看到详细的介绍,在回答时只需要挑选几个你最常用的、最熟悉的特性即可。