TypeScript 使用手册
TypeScript 官方提供的编译器叫做 tsc,可以将 TypeScript 脚本编译成 JavaScript 脚本。本机想要编译 TypeScript 代码,必须安装 tsc。
安装、配置与命令
安装
使用命令全局安装:
pnpm install -g typescriptpnpm install -g typescript安装完成后,检查一下是否安装成功
tsc -vtsc -v输出当前安装的 tsc 版本。
命令参数
tsc命令后面,加上.ts文件,就可以将其编译成 JavaScript 脚本。
tsc main.tstsc main.ts多个文件编译:
tsc file1.ts file2.ts file3.tstsc file1.ts file2.ts file3.tstsc 有很多参数,具体如下:
--outFile
该参数可以将ts文件编译输出到一个指定的文件中:
tsc file1.ts file2.ts --outFile main.jstsc file1.ts file2.ts --outFile main.js上边命令可以将file1和file2编译后的内容输出到main.js
--outDir
指定保存到其他目录:
tsc main.ts --outDir disttsc main.ts --outDir dist上面命令会在dist子目录下生成main.js。
--target
使用--target参数,指定编译后的 JavaScript 版本。
tsc --target es2015 main.tstsc --target es2015 main.ts--noEmitOnError
如果编译过程中报错,tsc命令就会显示报错信息,但依旧会生成编译的结果。
如果希望一旦报错就停止编译,不生成编译产物,可以使用--noEmitOnError参数。
tsc --noEmitOnError main.tstsc --noEmitOnError main.ts上面命令在报错后,就不会生成app.js。
--noEmit
只检查类型是否正确,不生成 JavaScript 文件。
tsc --noEmit app.tstsc --noEmit app.ts--declaration(-d)
生成一个类型生成文件。
--emitDeclarationOnly
只编译输出类型声明文件,不输出 JS 文件。
--strict
打开 TypeScript 严格检查模式。
--watch(-w)
进入观察模式,只要文件有修改,就会自动重新编译。
--project(-p)
指定编译配置文件,或者该文件所在的目录。
具体tsc命令的示例:
# 使用 tsconfig.json 的配置
$ tsc
# 只编译 index.ts, 此时会忽略tsconfig.json文件配置
$ tsc index.ts
# 编译 src 目录的所有 .ts 文件
$ tsc src/*.ts
# 指定编译配置文件
$ tsc --project tsconfig.production.json
# 只生成类型声明文件,不编译出 JS 文件
$ tsc index.js --declaration --emitDeclarationOnly
# 多个 TS 文件编译成单个 JS 文件
$ tsc app.ts util.ts --target esnext --outfile index.js# 使用 tsconfig.json 的配置
$ tsc
# 只编译 index.ts, 此时会忽略tsconfig.json文件配置
$ tsc index.ts
# 编译 src 目录的所有 .ts 文件
$ tsc src/*.ts
# 指定编译配置文件
$ tsc --project tsconfig.production.json
# 只生成类型声明文件,不编译出 JS 文件
$ tsc index.js --declaration --emitDeclarationOnly
# 多个 TS 文件编译成单个 JS 文件
$ tsc app.ts util.ts --target esnext --outfile index.js更多命令参数请参考这里
tsconfig.json 配置文件
tsconfig.json是 TypeScript 项目的配置文件,放在项目的根目录,主要供tsc编译器使用。
tsc -p ./dirtsc -p ./dir如果不指定配置文件的位置,tsc就会在当前目录下搜索tsconfig.json文件,如果不存在,逐级向上搜索父目录。
使用 tsc 命令的--init参数自动生成:
tsc --inittsc --init它会生成默认的配置,你也可以使用别人预先写好的 tsconfig.json 文件,比如 @tsconfig/recommended和@tsconfig/node16。这里有具体的一下配置文件去参考。
下面将逐一说明其中配置的属性含义:
- exclude
exclude属性是一个数组,用来从编译列表中去除指定的文件。
- include
include属性指定所要编译的文件列表,既支持逐一列出文件,也支持通配符。
{
"include": ["**/*"],
"exclude": ["**/*.spec.ts"]
}{
"include": ["**/*"],
"exclude": ["**/*.spec.ts"]
}- extends
继承另一个tsconfig.json文件的配置。可以把共同的配置写成tsconfig.base.json,其他的配置文件继承该文件,这样便于维护和修改。
可以通过路径的方式继承本地文件:
{
"extends": "../tsconfig.base.json"
}{
"extends": "../tsconfig.base.json"
}也可以继承已发布的 npm 模块:
{
"extends": "@tsconfig/node12/tsconfig.json"
}{
"extends": "@tsconfig/node12/tsconfig.json"
}extends指定的tsconfig.json会先加载,然后加载当前的tsconfig.json。如果两者有重名的属性,后者会覆盖前者。
- files
指定编译的文件列表,按照顺序编译。如果其中有一个文件不存在,就会报错。如果文件较多,建议使用include和exclude属性。
{
"files": ["a.ts", "b.ts"]
}{
"files": ["a.ts", "b.ts"]
}- compilerOptions
compilerOptions属性用来定制编译行为:
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"allowJs": true,
"declaration": true,
"declarationDir": "./types",
"declarationMap": true,
"removeComments": true
}
}{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"allowJs": true,
"declaration": true,
"declarationDir": "./types",
"declarationMap": true,
"removeComments": true
}
}allowJs允许 TypeScript 项目加载 JS 脚本。编译时,也会将 JS 文件,一起拷贝到输出目录。alwaysStrict确保脚本以 ECMAScript 严格模式进行解析,默认为true。declaration设置编译时是否为每个脚本生成类型声明文件.d.ts。declarationDir设置生成的.d.ts文件所在的目录。declarationMap设置生成.d.ts类型声明文件的同时,还会生成对应的 Source Map 文件。module指定编译产物的模块格式。如果target是ES3或ES5,它的默认值是commonjs,否则就是ES6/ES2015。它可以取以下值:none、commonjs、amd、umd、system、es6/es2015、es2020、es2022、esnext、node16、nodenext。noEmit设置是否产生编译结果。如果不生成,TypeScript 编译就纯粹作为类型检查了。noImplicitAny设置当一个表达式没有明确的类型描述、且编译器无法推断出具体类型时,是否允许将它推断为any类型。默认为true,即只要推断出any类型就报错。noImplicitReturns设置是否要求函数任何情况下都必须返回一个值,即函数必须有return语句。noImplicitThis设置如果this被推断为any类型是否报错。noUnusedLocals设置是否允许未使用的局部变量。noUnusedParameters设置是否允许未使用的函数参数。target:指定编译产物的 JS 版本。默认是es3。removeComments移除 TypeScript 脚本里面的注释,默认为false。resolveJsonModule允许 import 命令导入 JSON 文件。rootDir设置源码脚本所在的目录。sourceMap设置编译时是否生成 SourceMap 文件。strict用来打开 TypeScript 的严格检查,默认是关闭的。这个设置相当于同时打开以下的一系列设置。
- alwaysStrict
- strictNullChecks
- strictBindCallApply
- strictFunctionTypes
- strictPropertyInitialization
- noImplicitAny
- noImplicitThis
- useUnknownInCatchVariables
esModuleInterop修复了一些 CommonJS 和 ES6 模块之间的兼容性问题。forceConsistentCasingInFileNames设置文件名是否为大小写敏感,默认为true。moduleResolution确定模块路径的算法,即如何查找模块。
它可以取以下四种值:
node:采用 Node.js 的 CommonJS 模块算法。node16或nodenext:采用 Node.js 的 ECMAScript 模块算法,从 TypeScript 4.7 开始支持。classic:TypeScript 1.6 之前的算法,新项目不建议使用。bundler:TypeScript 5.0 新增的选项,表示当前代码会被其他打包器(比如 Webpack、Vite、esbuild、Parcel、rollup、swc)处理,从而放宽加载规则,它要求module设为es2015或更高版本。
它的默认值与module属性有关,如果module为AMD、UMD、System或ES6/ES2015,默认值为classic;如果module为node16或nodenext,默认值为这两个值;其他情况下,默认值为Node。
paths设置模块名和模块路径的映射,paths基于baseUrl进行加载,所以必须同时设置后者。
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@bar/*": ["packages/bar*"]
}
}
}{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@bar/*": ["packages/bar*"]
}
}
}typeRoots设置类型模块所在的目录,默认是node_modules/@types,该目录里面的模块会自动加入编译。
{
"compilerOptions": {
"typeRoots": ["./typings", "./vendor/types"]
}
}{
"compilerOptions": {
"typeRoots": ["./typings", "./vendor/types"]
}
}数组的每个成员就是一个目录,它们的路径是相对于tsconfig.json位置。一旦指定了该属性,就不会再用默认值node_modules/@types里面的类型模块。
types:只有其中列出的模块才会自动加入编译。
{
"compilerOptions": {
"types": ["node", "jest", "express"]
}
}{
"compilerOptions": {
"types": ["node", "jest", "express"]
}
}上面的设置表示,默认情况下,只有./node_modules/@types/node、./node_modules/@types/jest和./node_modules/@types/express会自动加入编译,其他node_modules/@types/目录下的模块不会加入编译。
如果"types": [],就表示不会自动将所有@types模块加入编译。
lib值是一个数组,描述项目需要加载的 TypeScript 内置类型描述文件
{
"compilerOptions": {
"lib": ["dom", "es2021"]
}
}{
"compilerOptions": {
"lib": ["dom", "es2021"]
}
}TypeScript 内置的类型描述文件,主要有以下一些,完整的清单可以参考 TypeScript 源码。
- ES5
- ES2015
- ES6
- ES2016
- ES7
- ES2017
- ES2018
- ES2019
- ES2020
- ES2021
- ES2022
- ESNext
- DOM
- WebWorker
- ScriptHost
allowSyntheticDefaultImports允许import命令默认加载没有default输出的模块。noImplicitReturns设置是否要求函数任何情况下都必须返回一个值,即函数必须有return语句。noUnusedLocals设置是否允许未使用的局部变量。noUnusedParameters设置是否允许未使用的函数参数。noImplicitOverride设置子类是否允许覆盖父类的方法。可通过override关键字去放开。noFallthroughCasesInSwitch设置是否对没有break语句(或者return和throw语句)的 switch 分支报错,即case代码里面必须有终结语句(比如break)。noEmitOnError指定一旦编译报错,就不生成编译产物,默认为false。
更多的配置项查看这里。
{
"compilerOptions": {
"module": "ESNext",
"resolveJsonModule": true,
"moduleResolution": "Bundler",
"moduleDetection": "force", // 强制每个非声明文件都被视为一个模块。
"target": "ESNext",
"lib": [
"DOM",
"DOM.Iterable",
"ESNext"
],
"allowSyntheticDefaultImports": true, // 允许import命令默认加载没有default输出的模块。
"declaration": true,
"newLine": "lf", // newLine设置换行符
"strict": true,
"noImplicitReturns": true, // 要求函数任何情况下都必须返回一个值
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noEmitOnError": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"jsx": "preserve"
}
}{
"compilerOptions": {
"module": "ESNext",
"resolveJsonModule": true,
"moduleResolution": "Bundler",
"moduleDetection": "force", // 强制每个非声明文件都被视为一个模块。
"target": "ESNext",
"lib": [
"DOM",
"DOM.Iterable",
"ESNext"
],
"allowSyntheticDefaultImports": true, // 允许import命令默认加载没有default输出的模块。
"declaration": true,
"newLine": "lf", // newLine设置换行符
"strict": true,
"noImplicitReturns": true, // 要求函数任何情况下都必须返回一个值
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noEmitOnError": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"jsx": "preserve"
}
}这是我暂时自用的一个配置,具体使用查看这里。
TypeScript 模块与解析
模块导入与导出
任何包含 import 或 export 语句的文件,就是一个模块(module)。模块本身就是一个作用域,不属于全局作用域。模块内部的变量、函数、类只在内部可见,对于模块外部是不可见的。
为了区分类型和变量,TypeScript 引入了两个解决方法。
第一个方法是在 import 语句输入的类型前面加上type关键字。
import { type A, a } from './a';import { type A, a } from './a';第二个方法是使用import type 语句,这个语句只能输入类型,不能输入正常接口。
// 正确
import type { A } from './a';
// 报错
import type { a } from './a';// 正确
import type { A } from './a';
// 报错
import type { a } from './a';同样的,export 语句也有两种方法,表示输出的是类型。
type A = 'a';
type B = 'b';
// 方法一
export {type A, type B};
// 方法二
export type {A, B};type A = 'a';
type B = 'b';
// 方法一
export {type A, type B};
// 方法二
export type {A, B};TypeScript 还允许使用import * as [接口名] from "模块文件"输入 CommonJS 模块。
import * as fs from 'fs';
// 等同于
import fs = require('fs');import * as fs from 'fs';
// 等同于
import fs = require('fs');模块定位
模块定位是用来确定 import 语句和 export 语句里面的模块文件位置。编译参数moduleResolution,用来指定具体使用哪一种定位算法。常用的算法有两种:Classic和Node。
// 相对模块
import { TypeA } from './a';
// 非相对模块
import * as $ from "jquery";// 相对模块
import { TypeA } from './a';
// 非相对模块
import * as $ from "jquery";加载模块时,目标模块分为相对模块和非相对模块两种。
相对模块指的是路径以/、./、../开头的模块。相对模块的定位,是根据当前脚本的位置进行计算的。
import Entry from "./components/Entry";import { DefaultHeaders } from "../constants/http";import "/mod";
非相对模块指的是不带有路径信息的模块。由baseUrl属性或模块映射而确定的,通常用于加载外部模块。
import * as $ from "jquery";import { Component } from "@angular/core";
Classic 方法
相对模块以当前脚本的路径,查找b.ts和b.d.ts。
非相对模块一层层查找上级目录中是否存在b.ts和b.d.ts`。
Node 方法
Node 方法就是模拟 Node.js 的模块加载方法,也就是require()的实现方法。
计算相对模块的位置,如果当前路径/root/a有文件导入let x = require("./b");:
- 首先寻找
/root/a/b.ts是否存在,如果存在使用该文件。 - 其次寻找
/root/a/b.tsx是否存在,如果存在使用该文件。 - 其次寻找
/root/a/b.d.ts是否存在,如果存在使用该文件。 - 其次寻找
/root/a/b/package.json, 如果 package.json 中指定了一个types属性的话那么会返回该文件。 - 如果上述仍然没有找到,会查找
/root/a/b/index.ts,/root/a/b/index.tsx,/root/a/b/index.d.ts
非相对模块则是以当前脚本的路径作为起点,逐级向上层目录查找是否存在子目录node_modules。
- 当前目录的子目录
node_modules是否包含b.ts、b.tsx、b.d.ts。 - 当前目录的子目录
node_modules,是否存在文件package.json,该文件的types字段是否指定了入口文件,如果是的就加载该文件。 - 当前目录的子目录
node_modules里面,是否包含子目录@types,在该目录中查找文件b.d.ts。 - 当前目录的子目录
node_modules里面,是否包含子目录b,在该目录中查找index.ts、index.tsx、index.d.ts。 - 进入上一层目录,重复上面4步,直到找到为止。
路径映射
baseUrl
baseUrl用来手动指定脚本模块的基准目录,当baseUrl是一个.,表示基准目录就是tsconfig.json所在的目录。
{
"compilerOptions": {
"baseUrl": "."
}
}{
"compilerOptions": {
"baseUrl": "."
}
}path
paths字段指定非相对路径的模块与实际脚本的映射。
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"jquery": ["node_modules/jquery/dist/jquery"]
}
}
}{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"jquery": ["node_modules/jquery/dist/jquery"]
}
}
}加载模块jquery时,实际加载的脚本是node_modules/jquery/dist/jquery,它的位置要根据baseUrl字段计算得到。可以指定多个路径,当第一个路径不存在,那么就加载后一个,以此类推。
声明文件*.d.ts
类型声明文件里面只有类型代码,没有具体的代码实现。
类型声明文件也可以包括在项目的 tsconfig.json 文件里面,这样的话,编译器打包项目时,会自动将类型声明文件加入编译,而不必在每个脚本里面加载类型声明文件。
{
"compilerOptions": {},
"files": [
"src/index.ts",
"typings/moment.d.ts"
]
}{
"compilerOptions": {},
"files": [
"src/index.ts",
"typings/moment.d.ts"
]
}类型声明文件主要有以下三种来源。
- TypeScript 编译器自动生成。
- TypeScript 内置类型文件。
- 外部模块的类型声明文件,需要自己安装。
TypeScript 会自动加载node_modules/@types目录下的模块,但可以使用编译选项typeRoots改变这种行为。
{
"compilerOptions": {
"typeRoots": ["./typings", "./vendor/types"]
}
}{
"compilerOptions": {
"typeRoots": ["./typings", "./vendor/types"]
}
}上面示例表示,TypeScript 不再去node_modules/@types目录,而是去跟当前tsconfig.json同级的typings和vendor/types子目录,加载类型模块了。
默认情况下,TypeScript 会自动加载typeRoots目录里的所有模块,编译选项types可以指定加载哪些模块。
{
"compilerOptions": {
"types" : ["jquery"]
}
}{
"compilerOptions": {
"types" : ["jquery"]
}
}类型声明文件里面,变量的类型描述必须使用declare命令,否则会报错。
declare var声明全局变量declare function声明全局方法declare class声明全局类declare enum声明全局枚举类型declare namespace声明(含有子属性的)全局对象interface 和 type声明全局类型
使用 declare 不再会声明一个全局变量,而只会在当前文件中声明一个局部变量。
// types.d.ts
declare let foo:string;
interface Foo {} // 正确
declare interface Foo {} // 正确
export interface Data {
version: string;
}// types.d.ts
declare let foo:string;
interface Foo {} // 正确
declare interface Foo {} // 正确
export interface Data {
version: string;
}三斜杠命令
/// <reference path="" />
告诉编译器在编译时需要包括的文件,经常用来声明当前脚本依赖的类型文件。
注意: path参数必须指向一个存在的文件,若文件不存在会报错。path参数不允许指向当前文件。
/// <reference types="" />
告诉编译器当前脚本依赖某个 DefinitelyTyped 类型库
/// <reference types="node" />/// <reference types="node" />表示编译时添加 Node.js 的类型库,实际添加的脚本是node_modules目录里面的@types/node/index.d.ts。只应该用在.d.ts文件中。
/// <reference lib="..." />
显式包含内置 lib 库,等同于在tsconfig.json文件里面使用lib属性指定 lib 库。
/// <reference lib="es2017.string" />/// <reference lib="es2017.string" />es2017.string对应的库文件就是lib.es2017.string.d.ts。
类型系统
any & unknow & never
any 类型表示没有任何限制,该类型的变量可以赋予任意类型的值。它会“污染”其他变量,导致其他变量出错。
let x:any = 'hello';
let y:number;
y = x; // 不报错
y * 123 // 不报错
y.toFixed() // 不报错let x:any = 'hello';
let y:number;
y = x; // 不报错
y * 123 // 不报错
y.toFixed() // 不报错unknow 表示类型不确定,可能是任意类型。unknow 不能直接调用unknown类型变量的方法和属性。
let v1:unknown = { foo: 123 };
v1.foo // 报错
let v2:unknown = 'hello';
v2.trim() // 报错let v1:unknown = { foo: 123 };
v1.foo // 报错
let v2:unknown = 'hello';
v2.trim() // 报错never 类型为空,不包含任何值。不可能赋给它任何值,否则都会报错,但可以赋值给任意其他类型。
function f():never {
throw new Error('Error');
}
let v1:number = f(); // 不报错
let v2:string = f(); // 不报错function f():never {
throw new Error('Error');
}
let v1:number = f(); // 不报错
let v2:string = f(); // 不报错空集是任何集合的子集。TypeScript 有两个“顶层类型”(any和unknown),但是“底层类型”只有never唯一一个。
基本类型
TypeScript 继承了 JavaScript 的8种类型设计:
- boolean
- string
- number
- bigint
- symbol
- object
- undefined
- null
undefined 和 null 既可以作为值,也可以作为类型,取决于在哪里使用它们。任何其他类型的变量都可以赋值为undefined或null。
let age:number = 24;
age = null; // 正确
age = undefined; // 正确let age:number = 24;
age = null; // 正确
age = undefined; // 正确原始类型的值,都有对应的包装对象。只有当作构造函数使用时,才会返回包装对象。
Boolean()String()Number()
以上三个构造函数,执行后可以直接获取某个原始类型值的包装对象。
const s1:String = 'hello'; // 正确
const s2:String = new String('hello'); // 正确
const s3:string = 'hello'; // 正确
const s4:string = new String('hello'); // 报错const s1:String = 'hello'; // 正确
const s2:String = new String('hello'); // 正确
const s3:string = 'hello'; // 正确
const s4:string = new String('hello'); // 报错值类型
单个值也是一种类型,称为“值类型”;
let x:'hello';
x = 'hello'; // 正确
x = 'world'; // 报错let x:'hello';
x = 'hello'; // 正确
x = 'world'; // 报错TypeScript 推断类型时,遇到const命令声明的变量,如果代码里面没有注明类型,就会推断该变量是值类型。
// x 的类型是 "https"
const x = 'https';// x 的类型是 "https"
const x = 'https';如果赋值为对象,并不会推断为值类型。
// x 的类型是 { foo: number }
const x = { foo: 1 };// x 的类型是 { foo: number }
const x = { foo: 1 };父类型不能赋值给子类型,但反过来是可以的。
// 等号右侧4 + 1的类型,TypeScript 推测为number。
// number是5的父类型, 所以会报错
const x:5 = 4 + 1;// 等号右侧4 + 1的类型,TypeScript 推测为number。
// number是5的父类型, 所以会报错
const x:5 = 4 + 1;联合类型
联合类型是指多个类型组成的一个新类型,使用符号|表示。
联合类型A|B表示,任何一个类型只要属于A或B,就属于联合类型A|B。
联合类型可以与值类型相结合,表示一个变量的值有若干种可能。
let setting:true|false;
let gender:'male'|'female';let setting:true|false;
let gender:'male'|'female';如果一个变量有多种类型,读取该变量时,往往需要进行“类型区分”,否则会导致报错。
function printId(
id:number|string
) {
if (typeof id === 'string') {
console.log(id.toUpperCase());
} else {
console.log(id);
}
}function printId(
id:number|string
) {
if (typeof id === 'string') {
console.log(id.toUpperCase());
} else {
console.log(id);
}
}根据不同的值类型,返回不同的结果。
交叉类型
交叉类型指多个类型组成的一个新类型,使用符号&表示。
交叉类型A&B表示,任何一个类型必须同时属于A和B,才属于交叉类型A&B,即交叉类型同时满足A和B的特征。交叉类型常常用来为对象类型添加新属性。
type A = { foo: number };
type B = A & { bar: number };type A = { foo: number };
type B = A & { bar: number };类型B在A的基础上增加了属性bar。
类型工具
Exclude
Exclude<UnionType, ExcludedMembers>用来从联合类型UnionType里面,删除某些类型ExcludedMembers,组成一个新的类型返回。
type T1 = Exclude<'a'|'b'|'c', 'a'>; // 'b'|'c'
type T2 = Exclude<'a'|'b'|'c', 'a'|'b'>; // 'c'
type T3 = Exclude<string|(() => void), Function>; // string
type T4 = Exclude<string | string[], any[]>; // string
type T5 = Exclude<(() => void) | null, Function>; // null
type T6 = Exclude<200 | 400, 200 | 201>; // 400
type T7 = Exclude<number, boolean>; // numbertype T1 = Exclude<'a'|'b'|'c', 'a'>; // 'b'|'c'
type T2 = Exclude<'a'|'b'|'c', 'a'|'b'>; // 'c'
type T3 = Exclude<string|(() => void), Function>; // string
type T4 = Exclude<string | string[], any[]>; // string
type T5 = Exclude<(() => void) | null, Function>; // null
type T6 = Exclude<200 | 400, 200 | 201>; // 400
type T7 = Exclude<number, boolean>; // numberExclude<T, U>就相当于删除兼容的类型,剩下不兼容的类型。
Extract
Extract<UnionType, Union>用来从联合类型UnionType之中,提取指定类型Union,组成一个新类型返回。
type T1 = Extract<'a'|'b'|'c', 'a'>; // 'a'
type T2 = Extract<'a'|'b'|'c', 'a'|'b'>; // 'a'|'b'
type T3 = Extract<'a'|'b'|'c', 'a'|'d'>; // 'a'
type T4 = Extract<200 | 400, 200 | 201>; // 200type T1 = Extract<'a'|'b'|'c', 'a'>; // 'a'
type T2 = Extract<'a'|'b'|'c', 'a'|'b'>; // 'a'|'b'
type T3 = Extract<'a'|'b'|'c', 'a'|'d'>; // 'a'
type T4 = Extract<200 | 400, 200 | 201>; // 200如果参数类型Union不包含在联合类型UnionType之中,则返回never类型。
type T = Extract<string|number, boolean>; // nevertype T = Extract<string|number, boolean>; // neverInstanceType
InstanceType<Type>提取构造函数的返回值的类型,等同于构造函数的ReturnType<Type>。
type T = InstanceType<
new () => object
>; // objecttype T = InstanceType<
new () => object
>; // object由于 Class 作为类型,代表实例类型。要获取它的构造方法,必须把它当成值,然后用typeof运算符获取它的构造方法类型。
class C {
x = 0;
y = 0;
}
type T = InstanceType<typeof C>; // Cclass C {
x = 0;
y = 0;
}
type T = InstanceType<typeof C>; // Ctypeof C是C的构造方法类型,然后 InstanceType 就能获得实例类型,即C本身。如果类型参数不是构造方法,就会报错。
如果类型参数是any或never两个特殊值,分别返回any和never。
type T1 = InstanceType<any>; // any
type T2 = InstanceType<never>; // nevertype T1 = InstanceType<any>; // any
type T2 = InstanceType<never>; // neverNonNullable
NonNullable<Type>用来从联合类型Type删除null类型和undefined类型,组成一个新类型返回。
// string|number
type T1 = NonNullable<string|number|undefined>;
// string[]
type T2 = NonNullable<string[]|null|undefined>;
type T3 = NonNullable<number|null>; // number
type T4 = NonNullable<string|undefined>; // string
type T5 = NonNullable<null|undefined>; // never// string|number
type T1 = NonNullable<string|number|undefined>;
// string[]
type T2 = NonNullable<string[]|null|undefined>;
type T3 = NonNullable<number|null>; // number
type T4 = NonNullable<string|undefined>; // string
type T5 = NonNullable<null|undefined>; // never等同于求T & Object的交叉类型,由于 TypeScript 的非空值都属于Object的子类型,所以会返回自身;而null和undefined不属于Object,会返回never类型。
Omit
Omit<Type, Keys>用来从对象类型Type中,删除指定的属性Keys,组成一个新的对象类型返回。
interface A {
x: number;
y: number;
}
type T1 = Omit<A, 'x'>; // { y: number }
type T2 = Omit<A, 'y'>; // { x: number }
type T3 = Omit<A, 'x' | 'y'>; // { }interface A {
x: number;
y: number;
}
type T1 = Omit<A, 'x'>; // { y: number }
type T2 = Omit<A, 'y'>; // { x: number }
type T3 = Omit<A, 'x' | 'y'>; // { }指定删除的键名Keys可以是对象类型Type中不存在的属性,但必须兼容string|number|symbol。
Partial
Partial<Type>返回一个新类型,将参数类型Type的所有属性变为可选属性。
interface A {
x: number;
y: number;
}
type T = Partial<A>; // { x?: number; y?: number; }interface A {
x: number;
y: number;
}
type T = Partial<A>; // { x?: number; y?: number; }Required
Required<Type>返回一个新类型,将参数类型Type的所有属性变为必选属性。它与Partial<Type>的作用正好相反。
interface A {
x?: number;
y: number;
}
type T = Required<A>; // { x: number; y: number; }interface A {
x?: number;
y: number;
}
type T = Required<A>; // { x: number; y: number; }具体实现:
type Required<T> = {
[P in keyof T]-?: T[P];
};type Required<T> = {
[P in keyof T]-?: T[P];
};Pick
Pick<Type, Keys>返回一个新的对象类型,第一个参数Type是一个对象类型,第二个参数Keys是Type里面被选定的键名。
interface A {
x: number;
y: number;
}
type T1 = Pick<A, 'x'>; // { x: number }
type T2 = Pick<A, 'x'|'y'>; // { x: number; y: number }interface A {
x: number;
y: number;
}
type T1 = Pick<A, 'x'>; // { x: number }
type T2 = Pick<A, 'x'|'y'>; // { x: number; y: number }指定的键名Keys必须是对象键名Type里面已经存在的键名,否则会报错。
Record
Record<Keys, Type>返回一个对象类型,参数Keys用作键名,参数Type用作键值类型。
// { a: number }
type T = Record<'a', number>;// { a: number }
type T = Record<'a', number>;参数Keys可以是联合类型,这时会依次展开为多个键。
// { a: number, b: number }
type T = Record<'a'|'b', number>;// { a: number, b: number }
type T = Record<'a'|'b', number>;如果参数Type是联合类型,就表明键值是联合类型。
// { a: number|string }
type T = Record<'a', number|string>;// { a: number|string }
type T = Record<'a', number|string>;参数Keys的类型必须兼容string|number|symbol,否则不能用作键名,会报错。
Readonly
Readonly<Type>返回一个新类型,将参数类型Type的所有属性变为只读属性。
interface A {
x: number;
y?: number;
}
// { readonly x: number; readonly y?: number; }
type T = Readonly<A>;interface A {
x: number;
y?: number;
}
// { readonly x: number; readonly y?: number; }
type T = Readonly<A>;具体实现:
type Readonly<T> = {
+readonly [P in keyof T]: T[P];
};type Readonly<T> = {
+readonly [P in keyof T]: T[P];
};可以自定义类型工具Mutable<Type>,将参数类型的所有属性变成可变属性。
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};+readonly就表示增加只读标志,等同于readonly。
ReturnType
ReturnType<Type>提取函数类型Type的返回值类型,作为一个新类型返回。
type T1 = ReturnType<() => string>; // string
type T2 = ReturnType<() => {
a: string; b: number
}>; // { a: string; b: number }
type T3 = ReturnType<(s:string) => void>; // void
type T4 = ReturnType<() => () => any[]>; // () => any[]
type T5 = ReturnType<typeof Math.random>; // number
type T6 = ReturnType<typeof Array.isArray>; // booleantype T1 = ReturnType<() => string>; // string
type T2 = ReturnType<() => {
a: string; b: number
}>; // { a: string; b: number }
type T3 = ReturnType<(s:string) => void>; // void
type T4 = ReturnType<() => () => any[]>; // () => any[]
type T5 = ReturnType<typeof Math.random>; // number
type T6 = ReturnType<typeof Array.isArray>; // boolean如果参数类型是泛型函数,返回值取决于泛型类型。如果泛型不带有限制条件,就会返回unknown。
type T1 = ReturnType<<T>() => T>; // unknowntype T1 = ReturnType<<T>() => T>; // unknown如果类型不是函数,会报错。
type T1 = ReturnType<boolean>; // 报错type T1 = ReturnType<boolean>; // 报错如果类型是any和never两个特殊值,会分别返回any和never。
Uppercase
Uppercase<StringType>将字符串类型的每个字符转为大写。
Lowercase
Lowercase<StringType>将字符串的每个字符转为小写。
Capitalize
Capitalize<StringType>将字符串的第一个字符转为大写。
Uncapitalize
Uncapitalize<StringType> 将字符串的第一个字符转为小写。
interface
interface 可以看作是一种类型约定,中文译为“接口”。它可以表示对象的各种语法,它的成员有5种形式。
(1) 对象属性
interface Point {
x: number;
y: number;
}interface Point {
x: number;
y: number;
}如果属性是只读的,需要加上readonly修饰符。
(2)对象的属性索引
interface A {
[prop: string]: number;
}interface A {
[prop: string]: number;
}表示属性名只要是字符串,都符合类型要求。属性索引共有string、number和symbol三种类型。一个接口中最多只能定义一个数值索引。
(3)对象的方法
// 写法一
interface A {
f(x: boolean): string;
}
// 写法二
interface B {
f: (x: boolean) => string;
}// 写法一
interface A {
f(x: boolean): string;
}
// 写法二
interface B {
f: (x: boolean) => string;
}其中类型方法可以重载,interface 里面的函数重载,不需要给出实现,需要额外在对象外部给出函数方法的实现。
(4)函数
interface 也可以用来声明独立的函数。
interface Add {
(x:number, y:number): number;
}
const myAdd:Add = (x,y) => x + y;interface Add {
(x:number, y:number): number;
}
const myAdd:Add = (x,y) => x + y;接口Add声明了一个函数类型。
(5)构造函数
interface ErrorConstructor {
new (message?: string): Error;
}interface ErrorConstructor {
new (message?: string): Error;
}interface 内部可以使用new关键字,表示构造函数。
interface的继承
继承interface
interface 可以使用extends关键字,继承其他 interface。
interface Shape {
name: string;
}
interface Circle extends Shape {
radius: number;
}interface Shape {
name: string;
}
interface Circle extends Shape {
radius: number;
}extends关键字会从继承的接口里面拷贝属性类型,这样就不必书写重复的属性,并且允许多重继承。
INFO
注意,子接口与父接口的同名属性必须是类型兼容的,不能有冲突,否则会报错。
多重继承时,如果多个父接口存在同名属性,那么这些同名属性不能有类型冲突,否则会报错。
继承type
interface 可以继承type命令定义的对象类型。
type Person = {
name: string;
age: number;
}
interface Kid extends Person {
hobbies: string[];
}type Person = {
name: string;
age: number;
}
interface Kid extends Person {
hobbies: string[];
}INFO
注意,如果type命令定义的类型不是对象,interface 就无法继承。
继承 class
继承 class,继承该类的所有成员。
class A {
x:string = '';
y():boolean {
return true;
}
}
interface B extends A {
z: number
}class A {
x:string = '';
y():boolean {
return true;
}
}
interface B extends A {
z: number
}B继承了A,因此B就具有属性x、y()和z。
interface 合并
多个同名接口会合并成一个接口。
interface Box {
height: number;
width: number;
}
interface Box {
length: number;
}interface Box {
height: number;
width: number;
}
interface Box {
length: number;
}日常开发中经常会对window对象和document对象添加自定义属性,但是 TypeScript 会报错,因为原始定义没有这些属性。解决方法就是把自定义属性写成 interface,合并进原始定义。
interface Document {
foo: string;
}
document.foo = 'hello';interface Document {
foo: string;
}
document.foo = 'hello';同名接口合并时,同一个属性如果有多个类型声明,彼此不能有类型冲突。
interface 与 type 的异同
很多对象类型既可以用 interface 表示,也可以用 type 表示。几乎所有的 interface 命令都可以改写为 type 命令。
interface 与 type 的区别有下面几点。
(1)type能够表示非对象类型,而interface只能表示对象类型(包括数组、函数等)。
(2)interface可以继承其他类型,type不支持继承。
type定义的对象类型如果想要添加属性,只能使用&运算符,重新定义一个类型。
type Animal = {
name: string
}
type Bear = Animal & {
honey: boolean
}type Animal = {
name: string
}
type Bear = Animal & {
honey: boolean
}类型Bear在Animal的基础上添加了一个属性honey。
继承时,type 和 interface 是可以换用的。interface 可以继承 type。type 也可以继承 interface。
interface Foo {
x: number;
}
type Bar = Foo & { y: number; };interface Foo {
x: number;
}
type Bar = Foo & { y: number; };(3)同名interface会自动合并,同名type则会报错。
type A = { foo:number }; // 报错
type A = { bar:number }; // 报错type A = { foo:number }; // 报错
type A = { bar:number }; // 报错(4)interface不能包含属性映射(mapping)。
interface Point {
x: number;
y: number;
}
// 正确
type PointCopy1 = {
[Key in keyof Point]: Point[Key];
};interface Point {
x: number;
y: number;
}
// 正确
type PointCopy1 = {
[Key in keyof Point]: Point[Key];
};(5)this关键字只能用于interface。
// 正确
interface Foo {
add(num:number): this;
};
// 报错
type Foo = {
add(num:number): this;
};// 正确
interface Foo {
add(num:number): this;
};
// 报错
type Foo = {
add(num:number): this;
};(6)type 可以扩展原始数据类型,interface 不行。
// 正确
type MyStr = string & {
type: 'new'
};
// 报错
interface MyStr extends string {
type: 'new'
}// 正确
type MyStr = string & {
type: 'new'
};
// 报错
interface MyStr extends string {
type: 'new'
}