TypeScript 入门与实战
-
tsconfig.json 编译配置文件 compilerOptions
- 启用
--strict
编译选项,开启严格模式 - 启用
--strictNullChecks
,undefined 值只能够赋值给 undefined 类型,null 值只能够赋值给 null 类型,实际上这种表述不完全准确。因为在该模式下,undefined 值和 null 值允许赋值给顶端类型,同时 undefined 值也允许赋值给 void 类型。 - 启用
--noImplicitAny
,禁用隐式 any 类型转换 - 启用
--noImplicitThis
,this 不能随意.任意值 - 启用
--strictPropertyInitialization
,类的成员变量初始化检查,需要与 --strictNullChecks 同时使用 - 启用
--strictFunctionTypes
,严格函数类型检查,false 时 对应位置上的参数只要存在子类型关系即可,而不强调谁是谁的子类型 - 启用
--noStrictGenericChecks
,非严格泛型函数类型检查,即忽略函数的参数类型 -
--importsNotUsedAsValues: remove|preserve|error
,如何处理 import type,删除/保留/强制 import type -
moduleResolution: Classic|Node
,默认与--module
相关:--module CommonJS --moduleResolution Node
,--module ES6 --moduleResolution Classic
- 启用
//#region //#endregion 定义了代码折叠区域的起止位置
-
es6 规范中定义的 7 种数据类型:Undefined、Null、Boolean、String、Symbol、Number、Object
- unique symbol:ts 中只允许用 const 或 readonly 来定义
- unique symbol:ts 中只允许用 Symbol()或 Symbol.for()初始化(“确保”其唯一性)
- unique symbol:ts 中将 Symbol.for('same')赋值给两个变量,编译器会认为它们是两个不同的值,即使他们相同。
-
枚举:无初始值,从前一个枚举成员的值+1(如果是首个为 0)
- 变量:数值型枚举 = number ✔︎; 变量:字符串枚举 = string ✘
- 在带有字符串成员的枚举中不允许使用计算值
- 枚举成员映射(仅数值型枚举):Bool[Bool.False] // 'False'
-
const enum
编译后会被替换成字面量,非const
会被转换成对象调用
-
any 与 unknown 的区别:any 为放弃类型检查,unknown 为未知类型。
- any 可以给任意类型变量赋值,nuknown 不可。
- any 可以进行任意操作,nuknown 几乎什么不能干(字符串、对象操作、数学运算等);
-
数组
-
const arr: (string | number)[] = [0, 'a']
与const arr: string | number[] = 'a'
不同 -
const arr: string[] = ['0', 'a']
与const arr: [string] = ['a']
不同 - 只读数组:
arr: ReadonlyArray
、arr: readonly string[]
。不能arr: readonly Array- 内置只读对象
type Readonly
= { readonly [P in keyof T]: T[P] } - 所以可以
arr: Readonly
、arr: Readonly
> - 只能访问不可变更,不可赋值给普通数组,但接受普通数组给自己赋值(这不是个 bug 吗)
- 内置只读对象
-
-
对象
-
const obj: object = { foo: 0 }obj.foo // err;
因为 object 类型下并没有 foo 属性 const arr12: { x: number } = { x: 0, y: 3 }; // err const arr12: { x: number } = { x: 0, y: 3 } as { x: number; y: number }; // ok const arr12: { x: number } = { x: 0, y: 3 } as { x: number; [key: string]: number }; //ok const temp = { x: 0, y: 3 }; const arr12: { x: number } = temp; // ok
-
-
函数
// 指定 this 结构 function foo(this: { name: string }, x: number): void { console.log(this.name, x); } foo.prototype.name = "foo"; new foo(3);
- 可以根据参数个数不同重载方法
-
Interface
- 索引签名
[prop: string]: Type
任意属性名、任意属性个数 - 重载的方法必须全部是不可选(可选),不可一些方法可选一些方法不可选
- 只读属性或方法
readonly count: number
- 继承多父接口时,父接口们的属性发生冲突,继承会报错,需要在子接口的重写,且重写的属性需要兼容所有父接口(冲突的属性的类型)
interface A { name: string } interface B { name: number } interface C extends A, B { name: any }
- 索引签名
-
类型别名 Type
-
type A = { name: string }; type B = A & { age: number }
效果与继承一样 - 与 Interface 的区别:
- 定义方法:
type foo = (x: number) => void;
、interface foo { (x: number): void }
; - type 可以使用联合类型、元组;interface 可继承其它 interface 或 class;
- interface 可以声明合并
interface A { name: string }; interface A { age: string };
- 定义方法:
-
-
类
-
const A = class B {}
“B” 只供类内部使用 - 使用非空类型断言“!”来通知编译器该成员变量已经进行初始化
class { a!: number; constructor() {} }
,以逃避编译器的检查 - 可以使用
[key: string]: number;
定义属性,但它不符合 ts 的宗旨 - 有 public、protected、private,主要依靠编译器的语法检查(ES10 中添加了 #attr 语法做为私有属性)
- constructor 可以重载
- 子类重写父类属性,只允许放宽,protected 可转为 public,不可转为 private
- 子类的构造函数必须先调用 super()再使用 this,且(ts 中)super()必须是第一个语句
- 实例化子类顺序:父类属性 -> 父类构造函数 -> 子类属性 -> 子类构造函
- extends 继承类;implements 实现接口。 类只支持单继承;implements 可以多实现
- 接口继承了含有非 public 属性的类,则此接口只能由该类的派生类实现(书 5.15.9 最后)
- static 静态属性(ES10),静态属性可继承(且修改其值不影响父类;不改子类改父类,子类也变;都改互不影响)
- 抽象类 abstract:不可实例化,可继承和被继承,被继承的子类可以实例化。抽象属性不可有具体实现,且不能是 private
-
-
泛型
function fn
,(arg: T): T { return arg; } fn
('xx') <定义类型 1,定义类型 2...>(arg: 使用类型): 使用类型;T 不能是 undefined 和 null
function fn
,(arg: T1): T2 {} fn
('xx') -
泛型约束(extends):T 可以继承一个类型,若指定的默认类型,默认类型必须符合继承的类型(泛型约束);实际传入的类型也必须符合泛型约束
function fn
,(arg: T) fn<2 | 3>(3)
-
泛型类描述的是类的实例类型,所以类的静态成员中不允许引用类型参数
class A
访问 tag 时可以使用{ static tag: T; } // 编译错误!静态成员不允许引用类型参数 A.tag
,泛型管不到静态属性
联合类型
type T = T1 | T2
满足一个就行;有同名不同类型的属性,不可混用。-
交叉类型
type T = T1 & T2
全部满足才行;有同名不同类型的属性,简单类型为 never,方法则重载。-
“&”相当于“×”,而“|”相当于“+”
T = (string | 0) & (number | 'a'); = (string & number) | (string & 'a') | (0 & number) | (0 & 'a'); = never | 'a' | 0 | never; = 'a' | 0;
-
-
索引类型
keyof Type
,keyof any => string|number|symbol
,keyof unknown => never
- 联合类型
keyof(T1 | T2)
共有的属性名。 - 交叉类型
type T = T1 & T2
全部满属性名。
- 联合类型
-
映射对象:
type newT = { readonly [K in keyof oldT]?: oldT[K] }
复制 oldT 将所有属性变为只读可选的(同态映射)-
[K in string]: number
等于[prop: string]: number
- 同态映射会默认拷贝源对象类型中所有属性的 readonly 修饰符和“?”修饰符
- 使用变量中转过的
keyof oldT
不再属于同态映射,所以不会拷贝原属性的修饰符:type EK = keyof oldT; type newT = { [K in EK]?: oldT[K] }
-
type Required = { +readonly [K in keyof T]-?: T[K] }
删除 ? 修饰符添加 readonly 修饰符;type T = { a?: string | undefined | null; readonly b: number | undefined | null; } // { // readonly a: string | null; // readonly b: number | undefined | null; // } type RequiredT = Required
; // - 号可以过删除 undefined,but not null - 更多类型的映射方式参见 6.6.4 同态映射对象类型
-
-
条件类型
-
type T = true extends boolean ? string: number;
=> string - 裸类型参数(Naked)即没有任何装饰([]、{}等)的类型参数;
- 如果
extends NakedType
(分布式条件类型)则条件展开; - 内置工具类型:
Exclude
、Extract
、Non-Nullable
从联合类型中过滤、挑选指定类型、创建非空类型; -
T extends Array
推断;? U : never inferType
=> number
-
-
内置工具类型(6.8 那么多,怎么可能记得住)
-
Partial
=>{ [K in keyof T]?: T[K] }
复制一份并全为可选 -
Required
=> -? 全为必选 -
Readonly
=> 全为只读 -
Record
=>{ name1: AttrType, name2: AttrType... }
-
Pick
=> 从 T 中选 AttrNames 这些属性创建新类型 -
Omit
=> Pick 的互补,AttrNames 为要忽略的属性 -
Exclude
=> 与 Omit 的区别为 Ks = keyof T -
Extract
=> 与 Pick 的区别为 Ks = keyof T -
NonNullable
=> 从类型 T 中剔除 null 类型和 undefined 类型并构造一个新类型 -
Parameters
=> 使用方法类型 fnT 的参数类型构造一个元组类型Parameters<(s: string) => void> // [string]
-
ConstructorParameters
=> 构造函数ConstructorParameters
void> // [string] -
ReturnType
=> 方法类型的返回值类型ReturnType<(s: string) => void> // void]
-
InstanceType
=> 获取构造函数的返回值类型,即实例类型 -
ThisParameterType
=> 方法中 this 参数的类型(需要启用“--strictFunctionTypes”编译选项) -
OmitThisParameter
=> 剔除 this 参数 -
ThisType
=> 不创建新类型 用于定义对象字面量的方法中 this 的类型???(需要启用“--noImplicitThis”)
-
类型查询,对 typeof 进行了扩展
const arg2: typeof arg1 = arg1
-
类型断言
expr -
=ele ele as HTMLElement
- 强制类型转换
ele as unknown as T
,复杂类型之间的类型断言,编译器可能会无法识别出正确的类型,因此错误地拒绝了类型断言操作 -
value as const
,只读字面量类型,如果 value 是个多级对象,只读作用于所有深度 - 非空类型断言“!”
-
-
类型细化
- 类型守卫:typeof、instanceof、in
- 断言函数:
function fn(x): asserts x is T
orasserts x
- 前者,只有 x 是 T 类型时,该函数才会正常返回,且此逻辑要由开发者自己实现;
- 后者,只有 x 为真时,才会正常返回,逻辑由开发者实现;
- 断言函数没有返回值。用于控制流程;
类型深入(第 7 章)
字面量类型:原始类型的子类型,比如具体的值
never <: undefined <: null <: 其它类型
函数类型重载:S 是 T 的子类型,且 T 存在函数重载,那么 T 的每一个函数重载必须能够在 S 的函数重载中找到与其对应的子类型
结构化子类型:根据对象的属性名和类型,直接判断两对象间的父子关系
实例的父子关系只检查非静态、非构造的成员属性,包含另一个的所有成员属性就是它是子类型。若有 protected 属性要求两者间有继承关系
泛型函数:noStrictGenericChecks 时只检查参数个数不检查类型,否则,两者间互相带入对方的类型进行推断
子类型在理论上可以赋值给父类型,any、数值型枚举除外。
ts 命名空间基于自执行函数实现通过“tsconfig.json”中 files 配置文件能够定义文件间的加载顺序
三斜线指令
///
定义文件间依赖(a.ts 不在 tsconfig.json 中定义也会被打包)-
模块
- CommonJS:nodejs,同步加载文件,不适用于浏览器
- AMD:异步模块定义,requirejs
- UMD:通用模块定义,基于上是 vue 打包后的样子
export ... from xxx
其它模块的导出作为当前模块的导出,xxx 中的功能不能在当前文件中使用。空导出使用是的模块的副作用
类型导入导出
import [type|interface] ...
,export [type|interface] ...
如果一个模块只导出类型,其副作用在打包时会被删除import(uri) => Promise
异步引入模块(es 语法,非 ts 独有)。Module 的值相当于import * as Module from 'uri'
编译
tsc index.ts --module [None|CommonJS|AMD|UMD|ES6|ES2020....]
-
外部类型声明
- *.d.ts
- declare const devicePixelRatio: number;
- declare module 'io' { export function readFile(uri: string): string; }
- declare module 'jquery'; 放弃对 jquery 插件进行类型检查
- 第三方插件没有 d.ts 声明文件时可以在 npm 官网使用
@types/插件名
搜索,或者于https://www.typescriptlang.org/dt/search?search=jquery
处查找 - package.json 文件中新增了 typings|types 属性指定包的声明文件,未指定默认为 main 同名同位置文件
-
模块解析
ts --moduleResolution [Classic|Node]
-
--baseUrl: '.'
非相对模块导入的基准路径 -
path: { "b": ["bar/b"], "@utils": ["./src/utils"] }
非相对模块导入的映射路径 -
rootDirs: ['src1', 'src2']
使用不同的目录创建出一个虚拟目录,相对模块导入的基准路径 - 外部模块声明
typings.d.ts
(vue 中访问不到 d.ts 中的自定义 type,目前看来是 eslint 的问题,没有解决)
-
-
声明合并
- 接口合并:同名 interface 会被合并成一个。属性出现同名不同类型会报错;方法出现同名不同类型会生成重载(入参为字面量的优先级高,后声明的优先级高);
- 枚举合并:同名 enum 会被合并成一个。有相同值的枚举合并报错;const 与非 const 枚举合并报错;
- 类合并:同名 class 不能合并。 但 declare class 可以和 interface 合并,当做对 class 类的扩展
-
扩充模块声明
import { A } from './a' declare module './a' { // 正确 interface A { other: string; } // 错误,不能扩充顶层声明 interface B { name: string; } } const a: A = { other: 'A中没有的属性' }
如果是
import './a'
无法对其扩充,interface 为必须 -
扩充全局声明
declare global { interface Window { other: string; } }
ts 配置
-
使用 tsc 指令时:
路径中的空格:
tsc 'file name.ts'
ortsc file\ name.ts
多个文件:使用空格分割,或者使用通配符
-w
:文件变更时自动重新编译-
严格类型:
--strict
为总开关:- noImplicitAny 禁用隐式 any 类型转换
- strictNullChecks 顶上
- strictFunctionTypes 顶上
- strictBindCallApply 对 call apply bind 中的 this 参数进行类型检查
- strictPropertyInitialization 类属性初始化检查
- noImplicitThis 顶上
- alwaysStrict 启用 js
use strict
-
tsconfig.json 若一个目录中存在此文件,该目录将被编译器视作 TypeScript 工程的根目录(或使用-p/--project tsconfig.json 来指定编译配置)。
tsc --init --locale zh-CN
当前目录初始化一个 tsconfig.jsoncompilerOptions > listFiles
打印出参与本次编译的文件列files
不支持模糊匹配,顶上include
定义编译文件列表,常与exclude
一起用,以剔除 include 中不需要的文件。它们都支持模糊匹配compilerOptions > typeRoots
默认指向 node_modules/@types;值为相对于 tsconfig.json 的路径列表compilerOptions > types
功能和 typeRoots 相同,但值为具体文件而非目录compilerOptions > isolatedModules
当编译器发现无法被正确处理的语言结构时给出提示tsc --showConfig
不编译代码,只显示当前配置extends
可以继承其它 tsconfig.json
-
工程引用:使用 references 引用其它工程,被引用的工程需要开启 composite
{ "references": [ { "path": "../dll1" }, { "path": "../dll2/tsconfig.release.json" } ] }
{ "compilerOptions": { "composite": true, "declaration": true, // 必须 "declarationMap": true // 必须,对d.ts生成sourceMap,转到定义时跳入真实代码位置而不是d.ts // 如果设置了 include 或 files,所有源文件必须都包含在内 } }
-
tsc [--build|-b] --watch
查找工程引用,构建引用(如果有更新)-
--verbose
:打印构建日志; -
--dry
:只要日志但不真构建; -
--clean
:清理输出文件; -
--force
:强制重新编译; -
--noEmit
:仅类型检查,不编译;
-
solution 工程:在所以工程的父目前创建 tsconfig.json,references 引用所有工程,include、files 设为空数组。
-
-
Javascript 类型检查
tsc index.js --allowJs --outDir dist
编译 js 文件--allowJs --checkJs
对 js 文件进行类型检查// @ts-nocheck
对当前 js\ts 禁用类型检查// @ts-check
对当前 js\ts 强制开启类型检查,无论有无--checkJs
// @ts-ignore
忽略下一行语句的类型检查-
JSDoc: 基于
/** xxx */
注释-
@typedef {(number | string )} CompanyID
生成自定义类型 CompanyID -
@type {CompanyID}
定义变量类型 -
@param {CompanyID} cpyId - 公司id
定义参数类型 -
@param {CompanyID} [cpyId=0] - 公司id
默认参数 @return {CompanyID}
-
@extends {FatherClass}
定义继承的基类 -
@public
、@protected
、@private
、@readonly
等等
-
TS 人家的例子
转译器
tsc index.js --allowJs --target ES5 --outFile index.es5.js
将 js 文件编译为 es5 语法-
babel7 提供了内置的 ts 支持
- @babel/core 核心转译功能
- @babel/cli 命令行工具
- @babel/preset-env 按需编译和按需打补丁
- @babel/preset-typescript ts 支持包
npm init --yes
cnpm i typescript -D
cnpm i @babel/core @babel/cli @babel/preset-env @babel/preset-typescript -D
npx babel [开发目录] --out-dir lib --extensions \".ts,.tsx\" --source-maps inline
// .babelrc.json
{
"presets": ["@babel/env", "@babel/typescript"]
}
名词
顶端类型:通用类型,超类型,ts 中指 any、unknown
尾端类型:never