TS文档学习 --- 常见类型(上)

296 阅读5分钟

本篇翻译整理自 TypeScript Handbook 中 「Everyday Types」 章节。

常见类型(Everyday Types)

本章我们会讲解 JavaScript 中最常见的一些类型,以及对应的描述方式。

类型可以出现在很多地方,不仅仅是在类型注解 (type annotations)中。我们不仅要学习类型本身,也要学习在什么地方使用这些类型产生新的结构。

我们先复习下最基本和常见的类型,这些是构建更复杂类型的基础。

原始类型(The primitives)

JavaScript 有三个非常常用的原始类型stringnumberboolean, 每一个类型在 TypeScript 中都有对应的类型。

他们的名字跟你在 JavaScript 中使用 typeof 操作符得到的结果是一样的。

类型名 StringNumberBoolean (首字母大写)也是合法的

但它们是一些非常少见的特殊内置类型, 也就是所谓的包装类类型。

所以类型总是使用 stringnumber 或者 boolean

数组(Array)

声明一个类似于 [1, 2, 3] 的数组类型,你需要用到语法 number[]

这个语法可以适用于任何类型(举个例子,string[] 表示一个字符串数组)。

你也可能看到这种写法 Array<number>,在功能上是一样的。

注意 [number]number[] 表示不同的数据类型

[number]表示的是元组类型

number[]表示的是数组类型

any

TypeScript 有一个特殊的类型,any,当你不希望一个值导致类型检查错误的时候,就可以设置为 any

当一个值是 any 类型的时候,你可以获取它的任意属性 (也会被转为 any 类型),或者像函数一样调用它,把它赋值给一个任意类型的值,或者把任意类型的值赋值给它,再或者是其他语法正确的操作,都可以:

let obj: any = { x: 0 };

// 以下所有代码都是合法的
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;

如果你没有指定一个类型,TypeScript 也不能从上下文推断出它的类型,编译器就会默认设置为 any 类型。

当你不想写长长的类型代码,仅仅想让 TypeScript 知道某段特定的代码是没有问题的,any 类型是很有用的。

尤其是你将JavaScript代码转移成TypeScript代码的时候

但是对于新的代码,并不推荐使用any。为此我们可以开启编译项 noImplicitAny

当被隐式推断为 any 时,TypeScript 就会报错。

变量上的类型注解(Type Annotations on Variables)

当我们声明一个变量时,你可以选择性的添加一个类型注解,显式指定变量的类型

let myName: string = "Klaus";

不过大部分时候,这不是必须的。因为 TypeScript 会自动推断类型

// myName的类型会被自动推测为string
let myName = "Klaus";

函数(Function)

函数是 JavaScript 传递数据的主要方法。TypeScript 允许你指定函数的输入值和输出值的类型。

参数类型注解(Parameter Type Annotations)

当你声明一个函数的时候,你可以在每个参数后面添加一个类型注解,声明函数可以接受什么类型的参数。参数类型注解跟在参数名字后面

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

即便你对参数没有做类型注解,TypeScript 依然会检查传入参数的数量是否正确

返回值类型注解(Return Type Annotations)

返回值的类型注解跟在参数列表后面:

function getFavoriteNumber(): number {
  return 26;
}

跟变量类型注解一样,你也不需要总是添加返回值类型注解,TypeScript 会基于它的 return 语句推断函数的返回类型。

匿名函数(Anonymous Functions)

匿名函数有一点不同于函数声明,当 TypeScript 知道一个匿名函数将被怎样调用的时候(例如作为回调函数)

匿名函数的参数会被自动的指定类型。

const names = ["Alice", "Bob", "Eve"];
 
// 上下文推断(contextual typing)
names.forEach(name => console.log(name.toUppercase();
  // Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});

对象类型(Object Types)

除了原始类型,最常见的类型就是对象类型了

TypeScript是结构性类型检测,我们只需要简单的列出它的属性和对应的类型即可

// 只要存在必要的属性,就是可以兼容的结构
// 类型有两个属性, x 和 y,两个都是 number 类型。你可以使用 , 或者 ; 分开属性,最后一个属性的分隔符加不加都行
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 });
可选属性(Optional Properties)

对象类型可以指定一些甚至所有的属性为可选的,你只需要在属性名后添加一个 ?

function printName(obj: { first: string; last?: string }) {
  // ...
}

// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });

联合类型(Union Types)

TypeScript 类型系统允许你使用一系列的操作符,基于已经存在的类型构建新的类型。

定义一个联合类型(Defining a Union Type)

一个联合类型是由两个或者更多类型组成的类型,表示值可能是这些类型中的任意一个。这其中每个类型都是联合类型的成员(members)

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 上的方法

如果我们需要访问某一个具体的类型上的属性或方法。解决方案是用代码收窄联合类型,当 TypeScript 可以根据代码的结构推断出一个更加具体的类型时,类型收窄就会出现。

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

有时候,如果联合类型里的每个成员都有一个属性,举个例子,数字和字符串都有 slice 方法,你就可以直接使用这个属性,而不用做类型收窄:

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