【翻译】TypeScript: Documentation - Everyday Types

日常类型: TypeScript 中的常用类型

The primitives: string, number, and boolean

基本类型: string,number, 和boolean

JavaScript有三种非常常用的基本类型string,number, 和boolean,这些类型分别对应TypeScript中的三种基本类型

  • string 表示字符串,如: 'Hello'
  • number 表示数字,如:1,2,3
  • boolean用于两个值truefalse

类型名称 StringNumberBoolean是合法的,但它们指的是一些很少出现在您代码中的特殊内置类型。始终使用 stringnumber boolean 作为类型。

String、NumberBoolean 类型分别对应 JavaScript 中的 String、NumberBoolean 对象。这些对象是 JavaScript 中原始字符串、数字和布尔值的包装对象,它们提供了一些额外的方法和属性。

Arrays

数组

要指定像[1,2,3]这样的数组类型,可以使用语法number[],这种语法适用于任何类型(例如: string[]是字符串数组,依次类推).此外,还可以使用Array<类型名称>的语法来指定数组的类型,它们的意思是一样的

any

可以在不希望特定值引起类型检查错误时使用它

当一个值的类型为any时,可以访问它的任何属性或者几乎任何语法上合法的其他操作,而不会引发类型检查错误

当你不想编写冗长的类型来说服TypeScript某行代码是正确的,any类型很有用

当您没有指定类型,且 TypeScript 无法从上下文推断出类型时,编译器通常会默认为 any

不过,您通常希望避免这种情况,因为 any 不会进行类型检查。使用编译器标志 noImplicitAny 来将任何隐式的 any 标记为错误。

在实际开发中,应尽量避免使用 any 类型。这是因为 any 类型会关闭 TypeScript 的类型检查功能,使您无法在编译时发现潜在的错误。

Type Annotations on Variables

变量上的类型注释

类型注释是一种语法,它允许您显式指定变量,函数参数和返回值等内容的类型

当你const,varlet声明变量时,可以选择添加类型注释来显式指定变量的类型

let myName: string = 'lisi'

然而,在大多数情况下,这并不是必须的.只要有可能,TypeScript会尝试自动推断您代码中的类型.例如,变量的类型是根据其初始化器的类型推断出来的

大多数情况下,您不需要显式学习推断规则。如果您刚开始使用 TypeScript,可以尝试使用比您想象的更少的类型注释 - 您可能会惊讶于 TypeScript 能够完全理解您的代码所需的类型注释数量之少。

Functions

函数

函数是在JavsScript中传递数据的主要方式.TypeScript允许您指定函数的输入和输出值的类型

Parameter Type Annotations

参数类型注释

当你声明一个函数,你可以在每个参数后添加类型注释,以声明该函数接受哪些类型的参数.参数类型注释在参数名词后面:

function greet(name: string) {
  console.log('Hello,' + name.toUpperCase()+ '!!')
}

当参数有了类型注释,将检查传递给该函数的参数

greet(42);
// Argument of type 'number' is not assignable to parameter of type 'string'.

即使您没有在参数上添加类型注释,TypeScript仍然会检查您传递的参数数量是否正确

Return Type Annotations

返回类型注释

您也可以添加返回类型注释。返回类型注释出现在参数列表之后。

function getFavoriteNumber(): number {
  return 26;
}

就像变量类型注释一样,您通常不需要返回类型注释,因为 TypeScript 会根据函数的返回语句推断函数的返回类型。上面示例中的类型注释并没有改变任何东西。有些代码库会为文档目的、防止意外更改或仅出于个人喜好而显式指定返回类型。

Anonymous Functions

匿名函数

匿名函数与函数声明有一些不同.当一个函数出现在TypeScript可以确定它将如何被调用的地方时,该函数的参数会自动获得类型

const names = ["Alice", "Bob", "Eve"];
names.forEach(function (s) {
  console.log(s.toUppercase());
});
names.forEach((s) => {
  console.log(s.toUppercase());
});

即使参数 s 没有类型注释,TypeScript 也会使用 forEach 函数的类型以及数组的推断类型来确定 s 的类型。

这个过程被称为上下文类型,因为函数出现的上下文决定了它应该具有的类型。

Object Types

对象类型

除了基本类型之外,您最常遇到的类型是对象类型。这指的是具有属性的任何 JavaScript 值,几乎所有的值都是这样!要定义一个对象类型,我们只需列出它的属性及其类型。

例如,这是一个接受类似点的对象的函数:

// 参数的类型注释是一个对象类型
function printCoord(pt: { x: number; y: number }) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });

在这里,我们用一个具有两个属性的对象类型注释了参数x y, 它们都是 number 类型。您可以使用 ,; 分隔属性,最后一个分隔符是可选的。

每个属性的类型部分也是可选的。如果您不指定类型,它将被假定为any类型。

Optional Properties

可选属性

对象类型还可以指定其部分或全部属性是可选的。要做到这一点,在属性名称后面添加一个 ?

function printName(obj: { first: string; last?: string }) {
  // ...
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });

JavaScript 中,如果您访问一个不存在的属性,您将获得 undefined 值而不是运行时错误。因此,当您从可选属性中读取时,您必须在使用它之前检查 undefined

function printName(obj: { first: string; last?: string }) {
  // Error - might crash if 'obj.last' wasn't provided!
  console.log(obj.last.toUpperCase());
  // 'obj.last' is possibly 'undefined'.
  if (obj.last !== undefined) {
    // OK
    console.log(obj.last.toUpperCase());
  }

  // A safe alternative using modern JavaScript syntax:
  console.log(obj.last?.toUpperCase());
}

Union Types

联合类型

TypeScript 的类型系统允许您使用多种运算符从现有类型构建新类型。现在我们已经知道如何编写一些类型,是时候开始以有趣的方式组合它们了。

Defining a Union Type

定义联合类型

组合类型的第一种方法是使用联合类型。联合类型是由两个或多个其他类型组成的类型,表示值可以是这些类型中的任何一个。我们将这些类型称为联合的成员。

function printId(id: number | string) {
  console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// Error
printId({ myID: 22342 });

Working with Union Types

使用联合类型

提供与联合类型匹配的值很容易 - 只需提供与联合的任何成员匹配的类型即可。如果您拥有联合类型的值,那么如何处理它呢?

TypeScript 只允许对联合的每个成员都有效的操作。例如,如果您有 string | number 联合类型,那么您不能使用仅在 string 类型上可用的方法。

// Property 'toUpperCase' does not exist on type 'string | number'.
// Property 'toUpperCase' does not exist on type 'number'.
function printId(id: number | string) {
  console.log(id.toUpperCase());
}

解决方案是使用代码缩小联合,就像您在没有类型注释的 JavaScript 中所做的那样。缩小发生在 TypeScript 根据代码的结构推断出更具体的类型时。这意味着您可以使用代码来检查值的类型,并根据检查结果执行不同的操作。

function printId(id: number | string) {
  if (typeof id === "string") {
    // In this branch, id is of type 'string'
    console.log(id.toUpperCase());
  } else {
    // Here, id is of type 'number'
    console.log(id);
  }
}

另一个例子是使用Array.isArray这样的函数:

function welcomePeople(x: string[] | string) {
  if (Array.isArray(x)) {
    // Here: 'x' is 'string[]'
    console.log("Hello, " + x.join(" and "));
  } else {
    // Here: 'x' is 'string'
    console.log("Welcome lone traveler " + x);
  }
}

注意,在 else 分支中,我们不需要做任何特殊的事情 - 如果 x 不是字符串数组,那么它一定是一个字符串。

有时您会遇到一个联合类型,其中所有成员都具有某些共同点。例如,数组和字符串都具有 slice 方法。如果联合的每个成员都具有一个共同的属性,那么您可以在不缩小的情况下使用该属性。

// Return type is inferred as number[] | string
function getFirstThree(x: number[] | string) {
  return x.slice(0, 3);
}

联合类型就像一个大容器,它可以容纳多种不同类型的值。但是当你想对这些值进行操作时,你只能进行所有成员都共有的操作

Type Aliases

类型别名

我们一直直接在类型注释中编写对象类型和联合类型。这很方便,但通常我们希望多次使用相同的类型并用单个名称引用它。

类型别名就是这样 - 任何类型的名称。类型别名的语法是:

type Point = {
  x: number;
  y: number;
};

// Exactly the same as the earlier example
function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}

printCoord({ x: 100, y: 100 });

实际上,您可以使用类型别名为任何类型命名,而不仅仅是对象类型。例如,类型别名可以命名联合类型。

type ID = number | string;

请注意,别名只是别名 - 您不能使用类型别名来创建相同类型的不同“版本”。

通常指的是无法直接修改或扩展现有的类型别名,以创建具有不同行为或属性的新类型。类型别名只是给现有类型起一个别名,本质上是同一个类型。

// 假设我们有一个类型别名MyString,表示字符串类型:
type MyString = string;
// 现在,如果我们尝试使用类型别名来创建一个新的版本,例如将字符串类型限制为只包含小写字母,我们无法通过类型别名直接实现:
type LowercaseString = MyString; // 无法通过类型别名直接创建新的版本
// 要实现此目标,需要使用其他特性,比如自定义类型或接口,或者使用类型转换函数等:
type LowercaseString = string;
function toLowercaseString(value: string): LowercaseString {
  return value.toLowerCase();
}
// 在上面的例子中,我们定义了一个名为LowercaseString的自定义类型,用于表示只包含小写字母的字符串。然后,我们编写了一个函数toLowercaseString,它接受一个普通的字符串作为参数,并将其转换为LowercaseString类型。

当您使用别名时,它就像您已经写过的别名类型一样。

这句话的意思是,当你使用类型别名时,它和直接使用被别名的类型是完全等价的。编译器会将类型别名替换为被别名的实际类型,就好像你直接写了被别名的类型一样。

type MyNumber = number;
function addNumbers(a: MyNumber, b: MyNumber): MyNumber {
  return a + b;
}
// 在这个例子中,虽然我们使用了类型别名MyNumber,但它实际上就是number类型的别名。因此,当我们调用addNumbers函数并传递两个参数时,编译器会将MyNumber替换为number,并执行函数的求和操作。

换句话说,这段代码看起来可能是非法的,但根据 TypeScript 是可以的,因为两种类型都是相同类型的别名。

type UserInputSanitizedString = string;

function sanitizeInput(str: string): UserInputSanitizedString {
  return sanitize(str);
}

// Create a sanitized input
let userInput = sanitizeInput(getInput());

// Can still be re-assigned with a string though
userInput = "new input";
type userInput = string;

let input: userInput = "hello";
input = "world"; // OK
input = 42; // Error: Type 'number' is not assignable to type 'string'.

由于 userInputstring 类型的别名,所以我们可以将任何字符串赋给变量 input ,但不能将其他类型的值赋给它。

类型别名不能重复

type userInput = string;
type userInput = number; // Cannot redeclare block-scoped variable 'userInput'.

Interfaces

接口

接口声明是命名对象类型的另一种方式。

interface Point {
  x: number;
  y: number;
}

function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}

printCoord({ x: 100, y: 100 });

TypeScript 是一种结构化类型系统,这意味着它只关心值的结构和能力,而不关心它们如何命名或定义。这意味着,当您使用接口或类型别名定义对象类型时,TypeScript 只关心对象是否具有预期的属性。

Differences Between Type Aliases and Interfaces

类型别名和接口之间的区别

类型别名和接口非常相似,在许多情况下,您可以自由选择它们。接口的几乎所有功能都在类型别名中可用,关键区别在于类型别名不能重新打开以添加新属性,而接口始终是可扩展的。

// 接口扩展
interface Animal {
  name: string;
}
interface Bear extends Animal {
  honey: boolean;
}
const bear = getBear();
bear.name;
bear.honey;
// 类型别名扩展
type Animal = {
  name: string;
};
type Bear = Animal & {
  honey: boolean;
};
const bear = getBear();
bear.name;
bear.honey;

它们之间的主要区别在于:

  • TypeScript 4.2 版本之前,类型别名名称可能会出现在错误消息中,有时会代替等效的匿名类型。而接口将始终在错误消息中命名

  • 类型别名不能参与声明合并,但接口可以。这意味着您可以通过扩展现有接口来添加新属性。

// 接口可以添加新值
interface Mammal {
  genus: string;
}
interface Mammal {
  breed: string;
}
const animal: Mammal = {
  genus: "1234",
  breed: "232",
};
// 类型别名不可以
type Reptile = {
  genus: string;
};
type Reptile = {
  breed: string;
};
// Error: Duplicate identifier 'Reptile'.
  • 接口只能用于声明对象的形状,不能重命名基本类型。这意味着您不能使用接口来为基本类型(如 stringnumber )定义新名称。
interface AnObject1 {
  value: string;
}
type AnObject2 = {
  value: string;
};
// 对于现有的基本类型,使用type我们可以创建自定义名称
type SanitizedString = string;
type EvenNumber = number;
// 这在接口上是不可行的
interface X extends string {}
  • 接口名称将始终以其原始形式出现在错误消息中,但仅当它们被名称使用时。

    当您使用接口时,如果发生错误,错误消息中将始终显示接口的名称.接口名称仅在按名称使用时才会出现在错误消息中。如果您使用匿名类型,则错误消息中不会显示接口名称。

interface Mammal {
  name: string;
}
function echoMammal(m: Mammal) {
  console.log(m.name);
}
// 错误消息中将显示接口的名称
echoMammal({ name: 12343 });

// 由于它没有直接命名为TypeScript不会在错误消息中提到它
function echoAnimal(m: { name: string }) {
  console.log(m.name);
}
echoAnimal({ name: 12345 });

大多数情况下,您可以根据个人喜好选择使用类型别名还是接口。如果您不确定应该使用哪种方式,可以先使用接口,直到您需要使用类型别名提供的功能。

Type Assertions

类型断言

是一种告诉 TypeScript 您比它更了解值类型的方法。它允许您指定一个更具体的类型,而不是 TypeScript 推断出的类型

有时您会拥有 TypeScript 无法知道的值类型的信息。

例如: 如果您使用 document.getElementByIdTypeScript 只知道这将返回某种 HTMLElement,但您可能知道您的页面上总是会有一个具有给定 IDHTMLCanvasElement

在这种情况下,您可以使用类型断言来指定更具体的类型。

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

类型断言就像类型注释一样,它们会被编译器删除,不会影响代码的运行时行为。

您还可以使用尖括号语法.这两种语法是等效的。但是,如果您的代码位于 .tsx 文件中,则不能使用尖括号语法,因为它与 JSX 语法冲突。

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

TypeScript 只允许将类型断言转换为更具体或更不具体的类型版本。这条规则防止了像这样的“不可能”的强制转换:

const x = "hello" as number;
// 将类型` string `转换为类型` number `可能是一个错误,因为两种类型都没有足够的重叠。如果这是故意的,请先将表达式转换为` unknown `。

有时这条规则可能过于保守,不允许更复杂的可能有效的强制转换。如果发生这种情况,您可以使用两个断言,先转换为 any(或稍后将介绍的 unknown),然后再转换为所需的类型。

const a = (expr as any) as T;

Literal Types

字面量类型

除了一般的stringnumber类型外,我们还可以在类型位置上引用特定的字符串和数字。

考虑这一点的一种方法是考虑 JavaScript 如何提供不同的方式来声明变量。var let 都允许更改变量内部保存的内容,而 const 则不允许。这反映在 TypeScript 如何为字面量创建类型。

let changingString = "Hello World";
changingString = "Olá Mundo";
// let changingString: string
// 因为`changingString`可以表示任何可能的字符串,所以
// TypeScript在类型系统中描述它为stirng

const constantString = "Hello World";
// const constantString: "Hello World"
// 因为`constantString`只能表示一个可能的字符串
// 所以有字面量类型表示

单独使用时,字面量类型并不是非常有价值的。

let x: "hello" = "hello";
// OK
x = "hello";
// Error
x = "howdy";

拥有一个只能有一个值的变量并没有太大用处!

但是,通过将字面量组合成联合,您可以表达更有用的概念 - 例如,只接受某些已知值集合的函数。

function printText(s: string, alignment: "left" | "right" | "center") {
  // ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
// 类型为` "centre" `的参数不能赋值给类型为` "left" | "right" | "center" `的参数。

数值字面量类型的工作方式相同:

function compare(a: string, b: string): -1 | 0 | 1 {
  return a === b ? 0 : a > b ? 1 : -1;
}

当然,你可以将它们与非字面量类型(non-literal types)结合使用:

interface Options {
  width: number;
}
function configure(x: Options | "auto") {
  // ...
}
configure({ width: 100 });
configure("auto");
configure("automatic");
// 类型为` "automatic" `的参数不能赋值给类型为` Options | "auto" `的参数。

还有一种字面量类型:布尔字面量。只有两种布尔字面量类型,正如您可能猜到的,它们分别是 truefalse 类型。boolean 类型本身实际上只是 true | false 联合的别名。

Literal Inference

字面量推断

当您使用对象初始化变量时,TypeScript 假定该对象的属性可能会在以后更改值。例如,如果您编写了这样的代码:

const obj = { counter: 0 };
if (someCondition) {
  obj.counter = 1;
}

TypeScript 不认为将 1 分配给先前为 0 的字段是错误的。换句话说,obj.counter 必须具有 number 类型,而不是 0,因为类型用于确定读写行为。

对字符串也适用相同的规则:

const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
// ` string `类型的参数不能赋值给` "GET" | "POST" `类型的参数。

在上面的示例中,req.method 被推断为 string,而不是 “GET”。因为在创建 req 和调用 handleRequest 之间可以评估代码,这可能会将新字符串 “GUESS” 分配给 req.method,所以 TypeScript 认为此代码存在错误。

有两种方法可以解决这个问题。

  1. 您可以通过在任一位置添加类型断言来更改推断:
// Change 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// Change 2
handleRequest(req.url, req.method as "GET");

Change 1的意思是“我打算让req.method始终具有字面类型“GET”,从而防止在之后将“GUESS”分配给该字段。” Change 2的意思是“我因为其他原因知道 req.method 的值为“GET”

  1. 你可以使用as const将整个对象转换为类型字面量:
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);

as const后缀的作用类似于const,但用于类型系统,确保所有属性都分配了字面类型,而不是像字符串或数字这样的更通用版本。

nullandundefined

JavaScript有两个原始值用于表示缺失或未初始化的值:nullundefined

TypeScript有两个相应的类型,名称相同。这些类型的行为取决于是否开启了strictNullChecks选项。

strictNullChecks off

如果关闭strictNullChecks,可能为nullundefined的值仍然可以正常访问,并且可以将nullundefined的值分配给任何类型的属性。这类似于没有空值检查的语言(例如C#Java)的行为。缺乏对这些值的检查往往是错误的主要来源;如果在他们的代码库中实际操作,我们总是建议人们打开strictNullChecks

strictNullChecks on

当开启strictNullChecks时,如果一个值为nullundefined,您需要在使用该值的方法或属性之前测试这些值。就像在使用可选属性之前检查undefined一样,我们可以使用缩小来检查可能为null的值。

function doSomething(x: string | null) {
  if (x === null) {
    // do nothing
  } else {
    console.log("Hello, " + x.toUpperCase());
  }
}

Non-null Assertion Operator (Postfix !)

非空断言运算符(后缀!

TypeScript还有一种特殊的语法,用于在不进行任何显式检查的情况下从类型中删除nullundefined。在任何表达式后写!实际上是一种类型断言,表示该值不是nullundefined

function liveDangerously(x?: number | null) {
  // No error
  console.log(x!.toFixed());

就像其他类型断言一样,这不会改变代码的运行时行为,因此只有在您知道该值不可能为nullundefined时才使用!非常重要。

Enums

枚举

枚举是TypeScript添加到JavaScript中的一项功能,它允许描述一个可能是一组可能的命名常量之一的值。与大多数TypeScript功能不同,这不是对JavaScript的类型级别添加,而是添加到语言和运行时中的东西。因此,这是一个您应该知道存在的功能,但除非您确定,否则可能会暂时不使用。您可以在枚举参考页面上阅读有关枚举的更多信息。

Less Common Primitives

不太常见的原始类型

值得一提的是,JavaScript 中其余的原始类型也在类型系统中表示。尽管我们不会在这里深入探讨。

bigint

ES2020 开始,JavaScript 中有一种用于表示非常大的整数的原始值 BigInt:

// 使用BigInt函数创建一个bigint对象

const oneHundred: bigint = BigInt(100);
 
// 通过字面量语法创建一个BigInt

const anotherHundred: bigint = 100n;

你可以在TypeScript 3.2的发行说明中了解有关BigInt的更多信息。

symbol

JavaScript中有一个原始类型,用于通过Symbol()函数创建全局唯一引用。

const firstName = Symbol("name");
const secondName = Symbol("name");
 
if (firstName === secondName) {
// 这种比较似乎是无意的,因为` typeof firstName `和` typeof secondName `类型没有重叠。
// 不可能发生
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值