Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescript 4.2英文文档 - Everyday Types #36

Open
qunzi0214 opened this issue May 25, 2021 · 0 comments
Open

Typescript 4.2英文文档 - Everyday Types #36

qunzi0214 opened this issue May 25, 2021 · 0 comments
Labels
read book 读书笔记

Comments

@qunzi0214
Copy link
Owner

qunzi0214 commented May 25, 2021

string, number, boolean

在 Typescript 存在3个常用的基本类型:string , number , boolean 。它们的命名和 Javascript 中对一个变量使用 typeof 操作符获得的值相同

使用 string number boolean 来做类型声明而非通过 String Number Boolean,因为后三者是某些场景下特定的内置类型

Arrays

想要指定一个数组的元素类型(比如 [1, 2, 3]),可以使用以下两种语法:

  • number[]
  • Array<number>

需要注意,[number] 是完全不同的东西,这种语法是用来声明元组的(确定元素数量与类型的数组)

any

在 Typescript 中存在一个特殊类型:any ,一旦某个变量被声明为 any ,Typescript 会在编译阶段放弃对它进行类型检查

这同时意味着,可以读写这个变量的任意属性、函数调用该变量、将任何类型的值复制给该变量或将该变量赋值给任意类型的其他变量

let obj: any = { x: 0 };
// None of the following lines of code will throw compiler errors.
// Using `any` disables all further type checking, and it is assumed 
// you know the environment better than TypeScript.
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;

需要注意,一旦你没有指定某个变量的类型且 Typescript 无法通过上下文对其进行类型推论,编译器会默认该变量类型为 any。如果需要避免这种情况,可以设置 noImplicitAny 选项

Type Annotations on Variables

如果通过 var let const 来声明一个变量,那么类型注释是可选的:

  • 通过类型注释显式声明变量类型
let myName: string = "Alice";
  • Typescript 自动对变量进行类型推论
// No type annotation needed -- 'myName' inferred as type 'string'
let myName = "Alice";

Functions

在 Typescript 中,允许你同时指定函数参数以及返回值的类型:

  • 参数类型注释
// Parameter type annotation
function greet(name: string) {
  console.log("Hello, " + name.toUpperCase() + "!!");
}

// Would be a runtime error if executed!
greet(42);
  • 函数返回值类型注释
function getFavoriteNumber(): number {
  return 26;
}

和变量类似,通常不需要对函数返回值进行类型注释,因为 Typescript 会根据 return 语句进行类型推论

匿名函数会有些不同,Typescript 会根据匿名函数如何被调用来决定此函数入参的类型:

// No type annotations here, but TypeScript can spot the bug
const names = ["Alice", "Bob", "Eve"];

// Contextual typing for function
names.forEach(function (s) {
  console.log(s.toUppercase());
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});

// Contextual typing also applies to arrow functions
names.forEach((s) => {
  console.log(s.toUppercase());
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});

Object Types

对象类型是除基本类型之外最常见的类型,定义一个对象类型,只需简单的罗列出它的属性和属性类型:

// The parameter's type annotation is an object type
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 });

定义对象类型,使用 ;, 分隔符都是合法的

可选属性:

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());
  // Object 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

一种方式结合不同的类型是使用联合类型:

function printId(id: number | string) {
  console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// Error
printId({ myID: 22342 });
// Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.
// Type '{ myID: number; }' is not assignable to type 'number'.

当在 Typesccript 中使用联合类型,只有当联合类型的每个成员都拥有某个方法和属性时,才允许访问:

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

解决办法是通过代码 narrow(变窄) 联合类型。Narrowing 通常发生在 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);
  }
}

如果联合类型中的成员同时具有某个方法或属性,不需要通过 Narrowing 也可以直接访问:

// 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;

需要注意,别名仅仅是别名,无法通过别名给同样的类型定义一个不同的、有区别的版本。

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 });

Type AliasesInterfaces 大多数情况非常类似,主要区别是别名无法通过重复定义的方式来新增属性:

  • 扩展 interface
interface Animal {
  name: string
}

interface Bear extends Animal {
  honey: boolean
}

const bear = getBear() 
bear.name
bear.honey
  • 扩展 type (通过交集)
type Animal = {
  name: string
}

type Bear = Animal & { 
  honey: Boolean 
}

const bear = getBear();
bear.name;
bear.honey;
  • 给已存在的 interface 新增字段:
interface Window {
  title: string
}

interface Window {
  ts: TypeScriptAPI
}

const src = 'const a = "Hello World"';
window.ts.transpileModule(src, {});
  • 给已存在的 type 新增字段:
type Window = {
  title: string
}

type Window = {
  ts: TypeScriptAPI
}

 // Error: Duplicate identifier 'Window'.

Type Assertions

某些情况下,你会比 Typescript 更了解某个值的类型信息。例如通过 document.getElementById ,Typescript 只知道会返回一个 HTMLElement 类型的值,但是你可能知道你的页面上会返回一个 HTMLCanvasElement 类型的值。

这种场景下,可以使用类型断言来指定一个更特定的类型:

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

和类型注释一样,类型断言会被编译器移除,不会影响代码运行时

另一种方式使用类型断言是通过尖括号语法( .tsx 文件不适用):

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

Typescript 仅允许通过类型断言将类型转换为更具体或更不具体的类型。该规则可防止出现“不可能”的强制转换:

const x = "hello" as number;
// Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

有时候,这条规则过于保守,禁止了一些更复杂但有效的强制转换。解决办法是通过两次类型断言(先断言为 anyunknown):

const a = (expr as any) as T;

Literal Types

除了基本类型 stringnumber,有时候需要指定某个更具体的字符串或数字

一种方式是 const

let changingString = "Hello World";
changingString = "Olá Mundo";
// Because `changingString` can represent any possible string, that
// is how TypeScript describes it in the type system
// let changingString: string

const constantString = "Hello World";
// Because `constantString` can only represent 1 possible string, it
// has a literal type representation
// const constantString: "Hello World"

鉴于通常一个可变的变量不会只持有一个值,以下这种方式不是很有价值:

let x: "hello" = "hello";
// OK
x = "hello";
// Type '"howdy"' is not assignable to type '"hello"'.
x = "howdy";

如果通过联合类型结合字面量类型,可以表达出一个非常有用的概念:例如函数只能接受某些确定的值

function printText(s: string, alignment: "left" | "right" | "center") {
  // ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
// Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.

数字的字面量类型工作方式是一样的:

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

当然,也可以结合字面量类型和非字面量类型:

interface Options {
  width: number;
}
function configure(x: Options | "auto") {
  // ...
}
configure({ width: 100 });
configure("auto");
configure("automatic");
// Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.

还有最后一种字面量类型:布尔值字面量类型,类型 boolean 本身就是布尔值字面量类型 true | false 的别名

在定义一个对象时,Typescript 不会将该对象的属性推论为字面量类型,例如以下例子中 counter 被推论为 number 类型而不是 0 类型:

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

考虑如下场景,req.method 被推论为 string,在 req 被初始化,和 handleRequest 被调用之间,该值可能会被重新赋予一个字符串,Typescript会认为这是个错误:

function handleRequest(url: string, method: "GET" | "POST")

const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
// Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

解决办法如下:

  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);

null and undefined

在 Javascript 中,有两个基本值 nullundefined 代表缺省或未初始化,Typescript 中同样有两个名字一致相应的类型,这两种类型如何表现取决于 strictNullChecks 选项是否打开。

  • strictNullChecks 关闭时:

nullundefined值可以正常访问,也可以用来赋予声明为任意类型的变量或属性。然而缺少了类型检查,这些值会导致大量bug,推荐打开 strictNullChecks 选项

  • strictNullChecks 打开时:

当一个值是 nullundefined,在使用这个值之前必须确保这个值拥有相应的属性或方法,类似于通过 Narrowing 来使用可选属性:

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

Non-null 类型断言操作符:

Typescript 中有一个特殊的语法,用来移除某个类型的 nullundefined,即表示,此值不可能为 nullundefined

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

和其他类型断言一样,这段代码不会对运行时产生影响,因此在使用 ! 操作符前,必须确保你知道这个值真的不可能是 nullundefined

Enums

枚举类型是对 Javascript 的一个扩展,允许给一组可以命名的值增加描述(太绕了,看例子),和其他 Typescript 特性不同,枚举类型是一个运行时的特性:

enum Color { Red, Blue, Green }

编译后的 Javascript :

var Color;
(function (Color) {
  Color[Color["Red"] = 0] = "Red";
  Color[Color["Blue"] = 1] = "Blue";
  Color[Color["Green"] = 2] = "Green";
})(Color || (Color = {}));

Less Common Primitives

bigint:ES2020之后,推出的一个新基本类型,用来存储一个非常大的整数

// Creating a bigint via the BigInt function
const oneHundred: bigint = BigInt(100);

// Creating a BigInt via the literal syntax
const anotherHundred: bigint = 100n;

symbol:通过 Symbol() 函数创建一个全局独一无二的引用类型:

const firstName = Symbol("name");
const secondName = Symbol("name");

if (firstName === secondName) {
// This condition will always return 'false' since the types 'typeof firstName' and 'typeof secondName' have no overlap.
  // Can't ever happen
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
read book 读书笔记
Projects
None yet
Development

No branches or pull requests

1 participant