术语帮助我们描述复杂的上下文,并防止我们在讨论代码时感到困惑。它在理解TypeScript和JavaScript方面起着重要的作用。
了解编程语言的控制结构很重要,能够命名它们的上下文和环境也同样重要。使用正确的词汇表在代码审查中特别有效,因为它支持我们用语言表达我们的想法。
-
算法与函数
-
算法
-
声明式编程
-
命令式编程
-
函数声明
-
函数表达式
-
箭头函数表达式
-
引用不透明表达式
-
引用透明表达式
-
块级作用域
-
函数作用域
-
方法与函数
-
确定性函数
-
恒等函数
-
纯函数
-
-
node . js
-
核心模块
-
-
TypeScript
-
环境上下文
-
环境模块
-
数组解构
-
断言函数
-
断言签名
-
装箱类型
-
集合类型
-
编译器
-
构造签名
-
声明文件
-
解构赋值
-
区分联合
-
降级
-
函数参数
-
函数重载
-
函数签名
-
函数
-
生成器
-
泛型
-
导人断言
-
导入赋值
-
导入省略
-
接口
-
交叉类型
-
字面量类型
-
查找类型
-
映射类型
-
模块增强
-
模块
-
非空断言操作符
-
非原始类型
-
原始类型
-
字符串字面量类型
-
标签模板
-
模板字面量类型
-
模板字面量
-
顶级类型
-
转译器
-
三斜线指令
-
元组类型
-
类型别名
-
类型注解
-
类型参数推断
-
类型参数
-
类型断言
-
类型擦除
-
类型保护
-
类型谓词
-
类型推断
-
类型窄化
-
类型变量
-
类型扩大
-
联合类型
-
弱类型
-
算法与函数
算法
算法是解决特定问题或执行计算的一组指令。在TypeScript中,算法可以用函数来实现。
算法特点
算法具有如下定义特征:
-
通用性:算法必须应用于一组已定义的输入。
-
确定性/唯一性:在任何时间点,最多有一种延续的可能性。每个指令步骤都是明确定义的。
-
确定性:给定特定的输入,算法将始终产生相同的输出。
-
有效性:算法在有限的时间/步骤内终止。算法的每次执行都会传递一个结果。
-
有限性:算法用有限的步骤来描述。
-
可行性:在可用资源下执行算法应该是可行的。
声明式编程
声明式编程是一种编程范式,它定义了应用程序所需的状态,而不显式列出必须执行的语句。简而言之,声明式编程定义了应用程序从头到尾的执行。
例子:
-
通过定义外观如何基于属性变化来改变组件的UI行为
命令式编程
命令式编程是一种编程范式,它使用语句来更改应用程序的状态。简而言之,命令式编程从头到尾定义了一组指令。
例子:
-
通过定义如何在打开某些东西之前关闭它来改变UI行为
函数声明
函数声明会被提升,并以以下方式编写:
function myFunction(): number {
return 1337;
}
函数表达式
函数表达式是赋值的一部分,不会被提升:
const myFunction = function (): number {
return 1337;
};
箭头函数表达式
ES6中引入的简洁的函数表达式编写形式:
const myFunction = (): number => 1337;
引用不透明表达式
当表达式不能被其返回值替换时,它是引用不透明的。
例子:
function today(): string {
return new Date().toISOString();
}
const isoDate = today();
在编写时,执行 today()
返回 '2021-09-22T12:45:25.657Z'
。
这个结果会随着时间的推移而改变,所以我们不能用
const isoDate = '2021-09-22T12:45:25.657Z'
替换
const isoDate = today()
这使得这个表达式引用不透明。
引用透明表达式
当表达式可以被其返回值替换时,它是引用透明的。
例子:
function sayHello(): string {
return `Hello!`;
}
const message = sayHello();
sayHello
函数总是返回相同的文本,因此我们可以安全地将表达式替换为 const message = Hello!
,从而使其引用透明。
块级作用域
要在JavaScript中强制使用块级作用域,可以使用 let
关键字。它使变量从其块的外部无法访问:
function myFunction(): void {
if (true) {
let myNumber = 1337;
}
// Will throw a `ReferenceError` because `myNumber` is not defined
return myNumber;
}
console.log(myFunction()); // Causes an uncaught `ReferenceError`
函数作用域
默认情况下,JavaScript是函数作用域,这意味着变量可以在函数中访问:
function myFunction(): void {
if (true) {
var myNumber = 1337;
}
// Variable `myNumber` can be accessed within the function although it is being used outside of its conditional if-block
return myNumber;
}
console.log(myFunction()); // 1337
方法 vs 函数
有时,术语“方法”被用作“函数”的同义词。在JavaScript和TypeScript中有一个明显的区别:当一个函数是对象或类的属性时,它被称为方法。这样做是因为函数 this
可以有不同的值,这取决于它们与对象或类实例的关联。
函数:
'use strict';
function someFunction(this: unknown): void {
console.log(`I am a function and my this value is "undefined"`, this);
}
someFunction();
方法:
'use strict';
const person = {
name: 'Benny',
someMethod: function () {
console.log(`I am a method and my this value is "object"`, this);
},
};
person.someMethod();
如果你将箭头函数表达式转换为方法,你会注意到 this
的值转换为全局对象:
'use strict';
const person = {
name: 'Benny',
someMethod: () => {
console.log(`I am an arrow function and my this value is the global object.`);
},
};
person.someMethod();
确定性函数
给定相同的输入,确定性函数总是产生相同的输出。这使得确定性函数的输出是可预测的,因为它不依赖于动态状态。
恒等函数
恒等函数返回给定给它的相同值:
例子:
function identityFunction(text: string): string {
return text;
}
纯函数
纯函数是确定性函数的子集。对于给定的特定输入,纯函数总是产生相同的结果。此外,它避免了I/O操作(如打印到控制台或写入磁盘),因此不会产生副作用。
纯函数不会改变其传递的参数,并且是引用透明的。
node . js
核心模块
核心模块是在Node.js中定义的,可以通过 node:
前缀来识别。
例子:
import assert from 'node:assert';
assert.equal('text', 'text');
TypeScript
环境上下文
默认情况下,TypeScript编译器不知道我们的代码将在哪个运行时环境中执行(例如Node.js v16, Electron v16, Chrome v94)。这就是为什么我们可以通过定义ambience / ambient context来帮助编译器了解这一点。
例子:
如果你在一个TypeScript不知道"world"对象的环境中运行代码,你可以使用 declare var
来定义这个上下文:
declare var world: {
name: string;
};
console.log(world.name);
环境模块
包含使用 declare module
语法的声明文件的包称为环境模块。环境模块的主要目的是为纯粹用JavaScript编写的代码提供类型,以便TypeScript可以利用它。
你可以在绝对类型存储库中找到很多环境模块,它们在npm注册表的@types命名空间下提供。一个非常流行的例子是@types/node_package,它声明了多个模块,如 buffer
模块:
buffer.d.ts
declare module 'buffer' {
import { BinaryLike } from 'node:crypto';
import { ReadableStream as WebReadableStream } from 'node:stream/web';
export const INSPECT_MAX_BYTES: number;
export const kMaxLength: number;
export const kStringMaxLength: number;
export const constants: {
MAX_LENGTH: number;
MAX_STRING_LENGTH: number;
};
export type TranscodeEncoding = 'ascii' | 'utf8' | 'utf16le' | 'ucs2' | 'latin1' | 'binary';
// ...
}
如果你打算使用一些普通的JavaScript代码进行测试,但你不想花时间为它编写类型定义,你可以使用一个简短的环境模块声明:
buffer.d.ts
declare module 'buffer';
数组解构
从数组中提取值为不同的变量称为数组解构。
例子:
const [bart, homer, marge] = ['Bart', 'Homer', 'Marge'];
断言函数
断言函数类似于用户定义的类型保护,允许开发人员表达他们对输入数据的类型和形状的假设。这些函数可以用来检查变量的类型和值。TypeScript的编译器会在设计时使用这些信息来快速反馈应该如何使用类型。如果未通过类型检查,还将在运行时执行断言函数以抛出错误。
例子:
function assertString(input: unknown): asserts input is string {
if (typeof input !== 'string') {
throw new Error('Not a string!');
}
}
断言签名
断言签名是一种特殊的语法,用于告诉编译器断言的预期结果。它是断言函数的组成元素。
例子:
asserts input is User
装箱类型
原始数据类型没有方法或属性。为了支持在原语上使用方法,需要使用一种称为自动装箱的机制。
自动装箱涉及在访问对象特定的属性或方法时,自动将基本数据类型(如number
、string
和boolean
)转换为各自的包装器对象(Number
、String
、Boolean
)。这使得原语暂时表现得像对象一样,可以访问额外的功能。
这里有一个例子来说明自动装箱:
console.log((72).toFixed(2)); // 72.00
箱体类型 Number
也可以表示为:
Number.prototype.toFixed.call(72, 2);
集合类型
集合类型将同类的所有字面值统一起来。集合类型可用于扩大类型。
示例:类型 number
是所有整数的集合类型,因为它包含不同的整数值:
const numberCollective: number = 72;
更多集合类型:
-
bigint
-
boolean
-
number
-
string
编译器
编译器将高级编程语言代码转换为低级机器代码或一些低级中间表示(例如Java中的字节码)。
构造签名
当使用 new
关键字调用JavaScript中的函数时,该函数将被用作构造函数。为了让这种行为在TypeScript中反映出来,你必须为你的构造函数提供一个构造签名:
例子:
interface CatConstructor {
// Construct signature
new (name: string): ICat;
}
interface ICat {
name: string;
meow(): string;
}
// Turning function into constructor function
const Cat = function (this: ICat, name: string) {
this.name = name;
} as unknown as CatConstructor;
// Prototype-based programming
Cat.prototype.meow = function (): string {
return 'Meow!';
};
const myCat = new Cat('Joey');
console.log(myCat.name); // "Joey"
console.log(myCat.meow()); // "Meow!"
声明文件
声明文件(也称为 .d.ts
文件)用于为没有任何类型注释的现有JavaScript代码提供类型信息。
当你导入一个JavaScript库时,TypeScript可能无法理解JavaScript代码中定义的类型和函数。这可能会导致类型安全问题,并使导入的代码难以在TypeScript项目中使用。
声明文件提供了一种解决这个问题的方法,它为JavaScript代码定义了类型和函数,以便TypeScript能够理解它们。它们本质上告诉TypeScript代码的形状,而不提供具体实现的信息。
例子:
index.d.ts
export function formToJSON(form: GenericFormData|GenericHTMLFormElement): object;
export function isAxiosError<T = any, D = any>(payload: any): payload is AxiosError<T, D>;
export function spread<T, R>(callback: (...args: T[]) => R): (array: T[]) => R;
export function isCancel(value: any): value is Cancel;
export function all<T>(values: Array<T | Promise<T>>): Promise<T[]>;
声明 vs 定义
声明向编译器引入标识符(如变量或函数),而定义为该标识符提供实现或值。
例子:
// Here is a declaration:
type Greet = (name: string) => void;
// Here is a definition:
function greet(name: string): void {
console.log(`Hello, ${name}!`);
}
解构赋值
解构赋值允许从数组和对象中提取数据到单独的变量中。它是为数组或对象的每个值创建新变量的简写。
例子:
// Destructuring an object
const object = {
age: 35,
firstName: 'Benny',
};
const { age, firstName } = object;
console.log(`${firstName} is ${age} years old.`);
// Destructuring an array
const array = [300, 500];
const [min, max] = array;
console.log(`The range is between ${min} and ${max}.`);
区分联合
区分联合(或标记联合类型)可以根据共享属性缩小类型范围。
在下面的示例中, Dog
和 Person
类型有一个名为 type
的共享属性。根据该属性的值,TypeScript可以将类型从 Dog | Person
缩小到 Dog
或 Person
:
type Dog = {
age: number;
name: string;
bark: () => void;
type: 'dog';
};
type Person = {
age: number;
name: string;
shout: () => void;
type: 'person';
};
function makeNoise(dogOrPerson: Dog | Person): void {
switch (dogOrPerson.type) {
case 'dog':
// Type is narrowed down to "Dog", so we can "bark":
dogOrPerson.bark();
break;
case 'person':
// Type is narrowed down to "Person", so we can "shout":
dogOrPerson.shout();
break;
}
}
因为这允许对类型进行区分,所以这种技术被称为区分联合。这个概念也存在于f#中(f#中的区分联合)。
如果共享多个属性,建议创建一个基类型,以便其他类型可以继承:
type Creature = {
age: number;
name: string;
};
type Dog = Creature & {
bark: () => void;
type: 'dog';
};
type Person = Creature & {
shout: () => void;
type: 'person';
};
type CreatureUnion = Dog | Person;
function makeNoise(dogOrPerson: CreatureUnion): void {
switch (dogOrPerson.type) {
case 'dog':
// Type is narrowed down to "Dog", so we can "bark":
dogOrPerson.bark();
break;
case 'person':
// Type is narrowed down to "Person", so we can "shout":
dogOrPerson.shout();
break;
}
}
降级
TypeScript中的降级是指将源代码转换为旧版本的ECMAScript/JavaScript的过程。
函数参数
传递给函数的变量叫做实参。函数期望接收的内容称为形参。
-
形参:这是在函数或方法定义中使用的变量。它是将传递给函数的实际值(参数)的占位符。形参在函数签名中指定。
-
实参:这是调用函数时传递给函数的实际值。它对应于函数的形参。实参是在函数调用期间提供给函数的特定数据。
总而言之,形参是函数期望接收的内容,实参是为满足该期望而传递的实际值。
函数重载
通过函数重载,您可以定义具有相同名称但具有不同输入和输出类型的多个函数。
当函数的可能输入类型数量有限且返回类型取决于输入类型时,这是一种有用的技术。当调用一个重载函数时,TypeScript编译器会寻找与输入参数最匹配的函数签名,并返回相应的类型。这使函数的用户不必指定类型参数,并改善了开发人员体验(DX)。
例子:
// Function Overload #1
function combine(x: number, y: number): number[];
// Function Overload #2
function combine(x: string, y: string): string[];
// Function Implementation
function combine(x: number | string, y: number | string) {
return [x, y];
}
combine('1', '2'); // string[] will be returned
combine(1, 2); // number[] will be returned
泛型与函数重载
如果可能的类型很多,而您又不知道用户输入的是什么类型,那么泛型就更适合了。泛型允许您指定类型参数,这可以使可以处理广泛输入类型的函数更容易工作。
然而,在某些情况下,函数重载更适合。例如,使使用操作符如 +
的函数可重用。
函数签名
函数签名在TypeScript中定义了函数的形状或结构。它指定函数的参数列表、返回类型以及可选的 this
类型。函数签名也可以用函数类型表示。
例子:
type MyFunctionSignature = (a: number, b: number) => number;
const add: MyFunctionSignature = (a, b) => {
return a + b;
};
函数
函数由几个定义其结构的构建块组成。以下是不同部分的细分:
-
函数名:这是用来调用函数的标识符。
-
函数签名:定义函数的形状,包括形参列表、
this
上下文和返回类型。 -
函数体:这是用花括号(
{}
)括起来的代码块。它包含定义函数行为的指令和语句。函数体中的代码在调用函数时执行。
生成器
生成器是可以返回多个连续值(称为yield)的函数。生成器函数使用"function"关键字后跟一个星号( function*
)声明。当调用生成器函数时,它返回一个实现可迭代协议和迭代器协议的对象。
迭代器协议定义了一个 next
函数,可用于检索生成器函数的最新值。
迭代器协议允许对象在for…of的结构中被遍历。为了与可迭代协议保持一致,对象必须在名为Symbol.iterator的属性中定义一个迭代器函数。
例子:
function* generateNumbers(amount: number) {
const data = Array.from({ length: amount }, (_, index) => index + 1);
for (const item of data) {
yield item;
}
}
// Initializing the generator
const numberGenerator = generateNumbers(10);
// Iterating through the generator
for (const number of numberGenerator) {
console.log(number);
}
泛型
泛型允许您为类型创建模板。
例子:
通过使用泛型,可以将输入参数与输出参数关联起来。下面的代码在调用泛型函数 combine
时将类型变量( T
)设置为 number
:
function combine<T>(a: T, b: T): T[] {
return [a, b];
}
combine<number>(1, 2); // [1, 2]
TypeScript在传递输入值时支持类型参数推断:
function combine<T>(a: T, b: T): T[] {
return [a, b];
}
combine(1, 2); // [1, 2]
你也可以指定多个类型变量:
function combine<X, Y>(a: X, b: Y): (X | Y)[] {
return [a, b];
}
combine(1, '2'); // [1, "2"]
也支持默认类型变量:
function combine<X, Y = string>(a: X, b: Y): (X | Y)[] {
return [a, b];
}
// It became optional to pass a type argument for `Y`:
combine<number>(1, '2'); // [1, "2"]
也可以使用 extends
关键字在泛型类型上强制执行特定的结构:
function combine<T extends { name: string }>(a: T, b: T): T[] {
return [a, b];
}
combine({ name: 'Benny', type: 'Pet' }, { name: 'Sofia' });
泛型也可以在类中工作:
class KeyValuePair<Value> {
public key: string | undefined;
public value: Value | undefined;
}
导入断言
TypeScript中的导入断言允许指定关于被导入模块的额外元数据。
例子:
import marketData from '../fixtures/2023-07-SHOP-USD.json' assert { type: 'json' };
从导入断言中产生的TC39语法建议称为导入属性。
导入赋值
当导入CommonJS模块时,导入赋值是TypeScript中典型的。一个导入赋值是这样的:
import Clipboard = require('clipboard');
当使用ECMAScript模块时,不能使用导入赋值,需要进行转换。如果CommonJS模块有一个默认的导出,那么它可以通过以下方式导入:
import { default as Clipboard } from 'clipboard';
导入省略
在TypeScript中,“导入省略”指的是如果导入只用于类型检查并且对运行时没有影响,就会分析并从生成的JavaScript代码中删除它们的行为。此优化过程旨在消除不必要的导入并优化输出JavaScript代码。
编译器选项 verbatimModuleSyntax
简化了导入省略的规则,因为它保留了没有 type
修饰符的导入和导出。带有 type
修饰符的导入和导出将被完全删除,以确保精简和可预测的结果。
接口
接口是为对象的形状定义契约的一种方式。它指定了对象必须被视为该接口实例的属性和方法的名称和类型。
接口可以用来指定对象的形状,定义类的结构,描述函数的签名。
例子:
interface Calculator {
(x: number, y: number): number;
}
interface ExtendedCalculator extends Calculator {
(x: string, y: string): number;
}
const add: Calculator = (a, b) => a + b;
const advancedAdd: ExtendedCalculator = (a, b) => {
return parseInt(`${a}`, 10) + parseInt(`${b}`, 10);
};
console.log(add(1000, 337)); // 1337
console.log(advancedAdd(1000, 337)); // 1337
console.log(advancedAdd('1000', '337')); // 1337
接口是编译时结构,这意味着TypeScript编译器在运行时不会为接口生成任何JavaScript代码。
最佳实践:
接口被认为是“类型”的更好版本,因为它们比交叉类型提高了性能,允许声明合并并支持模块扩展。
交叉类型
交叉类型将多个类型组合成一个类型。
例子:
type User = {
name: string;
};
type Address = {
city: string;
country: string;
};
// Intersection Type "Customer"
type Customer = User & Address;
const benny: Customer = {
city: 'Berlin',
country: 'Germany',
name: 'Benny',
};
字面量类型
字面量类型是集合类型的更具体的子类型。字面量类型可以表示精确的值,例如特定的数字或字符串,而不是可以是任何数字或字符串的一般值。
const numberLiteral: 72 = 72;
const stringLiteral: 'Benny' = 'Benny';
const booleanLiteral: true = true;
const objectLiteral = {
age: 35,
name: 'Benny',
};
enum ConnectionState {
OFF,
ON,
}
const enumLiteral = ConnectionState.OFF;
字面量缩小
缩小是指减少变量所能容纳的可能值的集合的过程。当使用 const
关键字声明变量时,TypeScript应用文本窄化:
// Type of "text" is "Hello, World!" (string literal)
const text = 'Hello, World!';
另一方面,当使用 let
关键字时,TypeScript会为变量推断出一个集合类型,比如 number
:
// Type of "text" is "string" (collective type)
let text = 'Hello, World!';
查找类型
查找类型,也称为索引访问类型,是一种通过名称或键检索对象属性类型的方法。当您想要编写使用动态属性名称的代码时,这是非常有用的,因为直到运行时才知道属性名称。
查找类型的语法如下: Type[Key]
,其中 Type
是对象的类型, Key
是要查找的属性的名称。
例子:
type Person = {
address: {
city: string;
country: string;
};
name: {
firstName: string;
lastName: string;
};
};
// Lookup types "Address" and "Name"
type Address = Person['address'];
type Name = Person['name'];
// Lookup types in action!
function getProperty(property: keyof Person, person: Person): Address | Name {
return person[property];
}
映射类型
映射类型是通过修改新类型中的属性名称或可访问性(例如 readonly
)从现有类型派生出来的。
例子:
// Existing Type
type User = {
age: number;
firstName: string;
lastName: string;
};
// Mapped Type
type PartialUser = {
[P in keyof User]?: User[P];
};
映射类型也可以与模板字面值一起使用。模板字面值是TypeScript的一个强大功能,它允许你创建包含表达式的字符串:
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type EndpointPaths = `/${string}` | `/${string}/${string}`;
type ApiEndpoints = { [K in `${HttpMethod} ${EndpointPaths}`]: string };
const myApi: ApiEndpoints = {
'GET /users': '/api/users',
'POST /users': '/api/users',
'PUT /users/123': '/api/users/123',
'DELETE /users/123': '/api/users/123',
};
模块增强
模块增强是通过将现有模块的声明与您自己的类型定义合并来扩展类型定义的过程。当您想要修改来自外部包的接口时,它特别有用。当来自外部包的类型定义被破坏并且需要修补时,就会出现这种情况。
例子:
在下面的例子中, webpack
模块中 Configuration
的现有定义被一个名为 devServer
的属性扩展:
import type { Configuration } from 'webpack';
declare module 'webpack' {
interface Configration {
devServer: {
open: boolean;
};
}
}
模块
在TypeScript中,模块是一个至少有一个顶层 import
或 export
语句的文件。
非空断言操作符
您可以使用“非空断言操作符”告诉编译器,您确定表达式不会是 null
或 undefined
。该操作符由位于表达式后面的感叹号( !
)表示。
在下面的代码片段中, name!
告诉TypeScript编译器假设 name
属性不是 null
或 undefined
。这允许访问 length
属性而不会引发编译器错误:
function getNameLength(user: { name?: string }): number {
return user.name!.length;
}
非原始类型
所有非原始数据类型,也称为复杂类型,都是通过引用传递的:
-
对象(和数组,它也是一个对象)
原始类型
所有原始数据类型都是按值传递的:
-
bigint
-
boolean
-
null
-
number
-
string
-
symbol
-
undefined
TypeScript的类型系统有以下原语:
-
unknown
-
any
字符串字面量类型
字符串字面量是一个特定的字符串(例如:"click"),字符串文字类型是描述特定字符串的类型:
const action: 'click' = 'click';
标签模板
import gql from 'graphql-tag';
const query = gql`
{
user(id: 5) {
firstName
lastName
}
}
`;
模板字面量类型
模板字面量类型是模板字面量和字符串字面量类型的组合。它能够在模板字符串中解析字符串插值,以便将其用于强类型:
type Action = 'click';
type ClickEvent = `${Action}Event`;
const myEvent: ClickEvent = 'clickEvent';
模板字面量
模板字面量是一个具有变量占位符的字面量(例如 ${placeholder}
),因此它可以作为文本的模板:
const templateLiteralString = `1 + 1 = ${1 + 1}`;
顶级类型
顶级类型,也称为通用类型,指的是作为特定类型系统中所有其他类型的父类型或超类型的类型。在TypeScript中,类型 any
和 unknown
被认为是顶级类型,因为它们包含了类型系统中的所有其他类型。
转译器
转译器将高级编程语言代码(如TypeScript)转换成另一种高级编程语言代码(如JavaScript)。
三斜杠指令
三斜杠指令是为TypeScript编译器提供指令的特殊注释。这些指令以三个斜杠( ///
)开头,放在文件的顶部或声明之前,以控制编译过程。
三斜杠指令最常见的用途是引用外部依赖项或声明文件。例如, /// <reference path="..." />
指令用于引用外部TypeScript声明文件( *.d.ts
),这些文件为当前文件中使用的库或模块提供类型信息。
三斜杠指令也可以用来配置某些编译器特性。例如,AMD模块指令( /// <amd-module />
)指定当前文件是AMD模块,并允许将可选模块名称传递给编译器。
需要注意的是,从TypeScript 3.0开始,管理依赖项和模块解析的首选方式是通过import语句。三斜杠指令主要用于遗留场景或兼容性原因。
元组类型
元组是数组的子类型。它类似于数组,因为它可以保存不同数据类型的元素,但不同之处在于它具有有限数量的元素。
例子:
type MyTupleType = [string, number];
const benny: MyTupleType = ['Benny', 34];
可变元组类型
可变元组类型是TypeScript中的一个特性,它允许无限数量的元素。定义可变元组类型的语法是使用rest语法( ...
)将多个元素压缩为一个:
type MyVariadicTupleType = [string, ...number[]];
const array: MyVariadicTupleType = ['prime numbers', 2, 3, 5, 7, 11];
在上面的代码示例中,与数组( Array<string | number>
)相比,使用可变元组类型的一个好处是,它强制元组的第一个元素必须为 string
类型。
标签元组元素
从TypeScript 4.0开始,你可以为元组类型的元素分配标签以提高可读性:
type MyTupleType = [name: string, age: number];
const benny: MyTupleType = ['Benny', 34];
元组与数组
元组具有固定数量的元素,数组具有可变数量的元素。
例子:
// Tuple, fixed size of elements
const id: [number] = [15];
// Array
const ids: number[] = [1, 2, 3];
// More and more elements can be pushed
ids.push(4);
元组类型由放在数据类型外面的方括号表示(例如 [number]
),而数组类型在数据类型后面有方括号(例如 number[]
):
const tuple: [string, number] = ['Benny', 34];
const array: (string | number)[] = ['Benny', 34];
类型别名
类型别名是为现有类型创建新名称的一种方法。
例子:
要将字符串表示为用户ID,可以创建 UserID
类型别名:
type UserID = string;
类型注解
类型注解是指显式定义变量可以采用的类型:
const x: number = 10;
类型参数推断
TypeScript可以推断类型,也可以推断类型参数。当泛型函数的参数连接到函数的泛型类型时,通常会发生这种情况。在这种情况下,TypeScript可以通过检查函数调用的输入值来推断类型变量的类型:
function yourGenericFunction<T>(input: T[]): number {
return input.length;
}
/** By inspecting the input of this function call, TypeScript will infer `yourGenericFunction<string>`. */
yourGenericFunction(['Benny']);
类型参数
在下面的代码中, <string>
是类型实参(有时称为类型形参):
const array = new Array<string>();
类型断言
类型断言是在TypeScript无法自动推断变量类型时告诉编译器该变量类型的一种方式:
const text = 'Hello, World!' as string;
小心使用类型断言是很重要的,因为它们可能导致危险的错误。这是因为类型断言覆盖了TypeScript的类型推断,如果使用不当,可能会导致给变量分配错误的类型:
// 💀: "Hello, World!" is NOT "Something else!"
const text = 'Hello, World!' as 'Something else!';
最佳实践:
类型断言在编写测试代码时特别方便。它们使你能够测试TypeScript代码是否能够通过对不正确的数据断言正确的类型来处理不正确的输入。这使得你可以将不正确的数据传递给被测代码,否则TypeScript的类型系统是不允许的。
当你需要重写TypeScript的类型推断时,建议使用断言函数而不是常规的类型断言。
相关:
-
断言函数
-
注释类型
类型擦除
在编译过程中,TypeScript会从输出中删除类型注解、接口、类型别名和其他类型构造。这种效果被称为“类型擦除”。
例子:
main.ts
const myName = 'Benny' as 'Rudolf';
类型断言 as 'Rudolf'
只存在于TypeScript的类型系统中,并且会在代码被翻译成JavaScript时被删除:
main.js
'use strict';const myName = 'Benny';
类型保护
类型保护是一个布尔表达式,它执行运行时检查以保证类型在特定范围内。
内置类型保护
-
typeof
-检查基本类型 -
in
-检查对象属性 -
instanceof
-检查类实例
用 typeof 关键字实现类型保护
typeof
类型保护可以保护基本类型。
function add1000(input: string | number): number {
if (typeof input === 'number') {
// Type guard helped us to detect a "number" (saves us from using "parseInt")
return input + 1000;
} else {
return parseInt(input, 10) + 1000;
}
}
当处理复杂类型时, typeof
类型保护可能不是很有用,因为复杂类型的类型总是 "object"
(见这里)。在这种情况下, in
类型保护将更有效。
用 in 关键字实现类型保护
type Dog = {
name: string;
bark: () => void;
run: () => void;
};
type Person = {
name: string;
shout: () => void;
walk: () => void;
};
function makeNoise(dogOrPerson: Dog | Person): void {
if ('bark' in dogOrPerson) {
// Type guard helped us to detect a "Dog"
dogOrPerson.bark();
} else {
dogOrPerson.shout();
}
}
虽然 in
类型保护对于检查普通对象是有效的,但是在检查 class
时建议
使用 instanceof
关键字实现类型保护
class Dog {
name?: string;
bark = () => {};
run = () => {};
}
class Person {
name?: string;
shout = () => {};
walk = () => {};
}
function makeNoise(dogOrPerson: Dog | Person): void {
if (dogOrPerson instanceof Dog) {
dogOrPerson.bark();
dogOrPerson.run();
} else {
dogOrPerson.shout();
dogOrPerson.walk();
}
}
自定义类型保护
function isAxiosError(error: unknown): error is AxiosError {
if (error && typeof error === 'object' && 'isAxiosError' in error) {
return true;
}
return false;
}
类型谓词
类型谓词是一种语法结构,用于定义类型保护的返回类型。它允许类型保护断言其输入的特定类型。下面是一个类型谓词的例子: error is AxiosError
类型推断
当没有显式的类型注释时,TypeScript会为你推断类型:
/** TypeScript infers `number` because "x" is initialized with a number and can be reassigned. */
let x = 10;
/** TypeScript infers `10` because "x" is a constant and cannot be reassigned. */
const x = 10;
/** TypeScript infers an array of `(string | number)` value types. */
const x = [10, '11'];
类型窄化
将宽类型细化为更具体的类型称为类型缩小。
概念:
类型变量
类型变量是泛型代码中泛型类型的占位符:
function yourGenericFunction<MyTypeVariable>(input: MyTypeVariable[]): number {
return input.length;
}
类型变量的编写方法是使用尖括号并为变量定义一个名称(例如 <T>
)。这个结构通常被称为菱形操作符,因为尖括号看起来像菱形( <>
,💎)。
类型扩大
当您将值赋给具有该值的超类型的变量时,您就扩大了类型。
// The value 72 is assigned to the wide type of "number"
const myVariable: number = 72;
概念:
联合类型
联合类型允许您指定值的可能类型范围:
type MyUnionType = string | number;
const myName: MyUnionType = 'Benny';
const myAge: MyUnionType = 34;
它被称为联合,因为它将可能的类型数量统一起来。术语并并来自集合理论,当两个(或多个)集合组合在一起时使用。
在区分联合中,联合类型变得非常强大。
字面联合类型
当您想要表示一组特定值(字面量)时,字面量联合类型可能很有用。例如,考虑以下字符串字面值联合类型:
type EventType = 'error' | 'info' | 'warn';
结合类型保护,文字联合类型可以使您的代码更具表现力和类型安全性。
弱类型
如果你的类型或接口的所有属性都是可选的,那么这个类型或接口被认为是弱的:
例子:
interface User {
age?: number;
firstName?: string;
lastName?: string;
}
欢迎关注公众号:文本魔术,了解更多