Skip to content

日常类型

函数类型

参数类型注释

ts
function greet(name: string) {
  console.log('hello', name);
}

返回值注释

ts
function returnStr(): string {
  return '123'
}

返回Promise

ts
async function getFavoriteNumber(): Promise<number> {
  return 26
}

匿名函数

会根据上下文推断参数类型

ts
const names: string[] = ['Aice', 'Bob', 'Eve']

names.map(s => {
  console.log(s.toLocaleLowerCase()); //编辑器会提示相应(string)的属性和方法
})

对象类型

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

联合类型

联合类型是由两种或多种其他类型组成的类型,表示可能是这些类型中的任何一种的值

ts
function printId(id: number | string) {
  console.log(id);
}

使用联合类型 提供与联合类型匹配的值很容易 - 只需提供与联合的任何成员匹配的类型即可。 如果你有一个联合类型的值,你如何处理它? TypeScript 只有在对联合的每个成员都有效的情况下才允许操作。 例如,如果你有联合 string | number,则不能使用仅在 string 上可用的方法:

ts
function printId(id: number | string) {
  // 这里不会提示 id 有 toUpperCase()方法,因为这个方法属于 string 类型
  console.log(id.toUpperCase()); 
}

解决方案是用代码缩小联合,就像在没有类型注释的 JavaScript 中一样。 当 TypeScript 可以根据代码的结构为某个值推断出更具体的类型时,就会发生缩小。

ts
function printId(id: number | string) {
  if (typeof id === 'string') {
    console.log(id.toUpperCase()); // 这里会有提示
  }
}

类型别名

ts
type Point = {
  x: number;
  y: number
}
ts
function printCoord(pt: Point) {
  console.log(pt.x, pt.y);
}

printCoord({x: 1, y: 2})

type ID = number | string

接口

ts
interface Name {
  first: string;
  last?: string
}

interface Name {
  fullName: string
}

类型别名(type)和接口(interface)的差别

类型别名和接口非常相似,在很多情况下你可以在它们之间自由选择。 interface 的几乎所有功能都在 type 中可用,主要区别在于type无法再次声明,和添加新属性,而interface始终可扩展。

添加新字段

ts
// interface
// 向现有接口添加新字段
// 不会被先后顺序影响,有点像js的作用域提升。
interface Animal { // 注意: 这里没用 `=`
  name: string
}
interface Animal {
  honey: boolean
}

// type
// 类型创建后无法更改
type Person = { // 注意: 这里有 `=`
  name: string,
}
type Person = { // Error: Duplicate identifier 'Window'.
  age: number | string
}

扩展

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

类型断言

有时你会得到关于 TypeScript 无法知道的值类型的信息。

例如,如果你使用的是 document.getElementById,TypeScript 只知道这将返回某种 HTMLElement,但你可能知道你的页面将始终具有具有给定 ID 的 HTMLCanvasElement。

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

ts

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

TypeScript 只允许类型断言转换为更具体或更不具体的类型版本。 此规则可防止 “impossible” 强制,例如:

ts
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.

有时,此规则可能过于保守,并且不允许可能有效的更复杂的强制转换。 如果发生这种情况,你可以使用两个断言,首先是 any(或 unknown,我们稍后会介绍),然后是所需的类型:

ts
const a = expr as any as T;

字面类型

除了stringnumber这些JavaScript类型外,我们还可以在类型位置使用特点的字符串和数字。

这和用const声明常量有点相似,

ts
// a和b都无法在修改成别的字符串
const a: 'hello' = 'hello'
let b: 'world' = 'world'
ts
interface Position {
  alignment: 'left' | 'right',
}

function printText(position: Position) {
  // ...
}
printText({ alignment: 'left' })

字面推断

当你使用对象初始化变量时,TypeScript 假定该对象的属性可能会在以后更改值.

ts
declare function handleRequest(url: string, method: 'GET' | 'POST'): void

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"'.

在上面的例子中, req.method 被推断为 string,而不是 "GET"。 因为可以在 req 的创建和 handleRequest 的调用之间评估代码,这可以将一个新的字符串(如 "GUESS" 分配给 req.method),TypeScript 认为此代码有错误。

以下方法可以解决这个问题

ts
//change1
// const req = { url: 'https://example.com', method: 'GET' as 'GET' }

//change2
// handleRequest(req.url, req.method as 'GET')

//change3
// const req = { url: 'https://example.com', method: 'GET' } as const