TS基础知识
TypeScript 手书、教程整理。
# TS与JS
TS是JS的超集,包含了最新的JS的功能,支持js不支持的强类型、泛型、模块、接口,需要编译成JS后在浏览器中使用。
编译后生成js文件,使用方法就和普通的js一样了。
所以,常见的项目工作流为:TS 编译生成 -> JS 打包 -> main.js 引用执行。
TS 包含所有最新的ES版本,从ES3 ~ ES2021(ES12)
# 使用TS
# 常用命令
# 安装命令
sudo npm install typescript -g
# 查看版本
tsc -v
# 生成tsconfig.json
tsc --init
# 编译某个文件
tsc file.name
# 编译ts,按照tsconfig.json配置,编译文件夹
tsc
# 编译并监听变化,动态更新编译结果
tsc --watch
2
3
4
5
6
7
8
9
10
11
12
其他参数 可以看handlebook中对tsconfig.json的介绍。
# 配置文件
官方初始化生成的ts配置表翻译对比 version = 4.6.2,保留原文的为翻译可能不正确
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* 增量编译 */
// "composite": true, /* 组合模式 */
// "tsBuildInfoFile": "./", /* 增量编译文件存储位置 */
// "disableSourceOfProjectReferenceRedirect": true, /* 组合模式下,不用源文件,使用 .d.ts 文件 */
// "disableSolutionSearching": true, /* 热更新时不完整编译整个项目 Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* 热更新时减少更新频率 Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* 目标js版本,默认ES3 */
// "lib": [], /* 编译中需要饮用的库列表;target=ES5默认为DOM,ES5,ScriptHost,target=ES6默认为target ES6:DOM,ES6,DOM.Iterable,ScriptHost */
// "jsx": "preserve", /* 支持的jsx模式 preserve为保留jsx文件 react为使用React.createElement */
// "experimentalDecorators": true, /* 启用试验性的ES装饰器 */
// "emitDecoratorMetadata": true, /* 支持元数据反射(ES7) */
// "jsxFactory": "", /* 配置jsx工厂函数使用React.createElement 或 h */
// "jsxFragmentFactory": "", /* 配置jsx片段使用React.Fragment 或 Fragment */
// "jsxImportSource": "", /* 配置导入的jsx工厂函数的模块 `jsx: react-jsx*` */
// "reactNamespace": "", /* 使用react、jsx时,配置createElement的调用对象 */
// "noLib": true, /* 不使用默认的库文件 lib.d.ts. */
// "useDefineForClassFields": true, /* 在class中声明的字段,使用 Object.defineProperty 来定义 */
/* Modules */
"module": "commonjs", /* 使用commonjs模式引入模块 */
// "rootDir": "./", /* 指定输入文件目录 */
// "moduleResolution": "node", /* 配置模块解析侧策略 */
// "baseUrl": "./", /* 配置非相对路径引入模块时的默认根目录 */
// "paths": {}, /* 模块-模块路径 映射 */
// "rootDirs": [], /* 配置源文件有多路径的情况 */
// "typeRoots": [], /* 声明文件目录 默认是 ./node_modules/@types. */
// "types": [], /* 声明文件包 */
// "allowUmdGlobalAccess": true, /* 允许在模块中访问UMD全局变量 */
// "resolveJsonModule": true, /* 允许导入.json文件 */
// "noResolve": true, /* 不编译 import, require or <reference> 导入的文件 */
/* JavaScript Support */
// "allowJs": true, /* 允许编译JS文件 */
// "checkJs": true, /* 编译js文件时,允许抛出错误 */
// "maxNodeModuleJsDepth": 1, /* nodemoudule 最大搜索深度 */
/* Emit */
// "declaration": true, /* 生成 .d.ts 文件 */
// "declarationMap": true, /* 给 .d.ts 文件 增加 sourcemap */
// "emitDeclarationOnly": true, /* 只生成 .d.ts 文件 不生成 js 文件 */
// "sourceMap": true, /* 生成.map文件 */
// "outFile": "./", /* 输出的文件名 */
"outDir": "./dist", /* 输出的文件目录 */
"removeComments": true, /* 去掉注释 */
// "noEmit": true, /* 不生成输出文件 */
// "importHelpers": true, /* 全局导入tslib */
// "importsNotUsedAsValues": "remove", /* 配置没有使用的导入语句应该如何处理 默认 remove 丢弃, preserve 保留, error 保留并允许抛出错误 */
// "downlevelIteration": true, /* 遍历器 iteration 的降级实现 (es3、es5) */
// "sourceRoot": "", /* 给ts文件加源文件路径 sourcemap */
// "mapRoot": "", /* 指定上面生成的sourcemap路径 */
// "inlineSourceMap": true, /* 合并生成的souecmap */
// "inlineSources": true, /* 将代码与sourcemao生成到一个文件里 */
// "emitBOM": true, /* 在生成的文件开头加BOM头(UTF-8 Byte Order Mark) */
// "newLine": "crlf", /* 指定行结束符是crlf 还是 lf */
// "stripInternal": true, /* 不对具有 /** @internal */ JSDoc注解的代码生成代码。 */
// "noEmitHelpers": true, /* 不在输出文件中生成用户自定义的帮助函数代码 */
// "noEmitOnError": true, /* 报错时不输出 */
// "preserveConstEnums": true, /* 保留 const 和 enum 声明 */
// "declarationDir": "./", /* 声明文件的输出路径 */
// "preserveValueImports": true, /* 是否保留没有使用的变量 */
/* Interop Constraints */
// "isolatedModules": true, /* 是否保证模块可以单独使用 */
// "allowSyntheticDefaultImports": true, /* 是否允许 import from 没有 export default 的模块 */
"esModuleInterop": true, /* 允许 export = XX */
// "preserveSymlinks": true, /* 不把符号链接解析为其真实路径 */
"forceConsistentCasingInFileNames": true, /* 禁止对同一个文件的不一致的引用 */
/* Type Checking */
"strict": true, /* 启用严格的检查选项 */
// "noImplicitAny": true, /* 禁止表达式和声明上有 隐含的 any */
// "strictNullChecks": true, /* null和undefined不包含在其他类型中,除了 void-undefined */
// "strictFunctionTypes": true, /* 禁止函数参数双向协变 */
// "strictBindCallApply": true, /* 严格检查bind、apply、call的参数是否与目标函数匹配 */
// "strictPropertyInitialization": true, /* 类的实例属性必须初始化 */
// "noImplicitThis": true, /* 禁止this表达式的值为any */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* 自动生成 use strict 语句 */
// "noUnusedLocals": true, /* 禁止有未使用的局部变量 */
// "noUnusedParameters": true, /* 禁止有未使用的参数 */
// "exactOptionalPropertyTypes": true, /* 明确定义的类型不支持赋值为 undefined */
// "noImplicitReturns": true, /* 禁止函数没有 return */
// "noFallthroughCasesInSwitch": true, /* 禁止switch中不合理使用break */
// "noUncheckedIndexedAccess": true, /* 使用索引访问属性时,该类型可能(没有定义类型等情况)被加上索引 */
// "noImplicitOverride": true, /* 使用 override 时,原型链上一定要有重名参数 */
// "noPropertyAccessFromIndexSignature": true, /* 允许一个类型有一个字符串索引的时候,使用点式调用 */
// "allowUnusedLabels": true, /* 允许未使用的标签错误 */
// "allowUnreachableCode": true, /* 允许执行不到的代码错误 */
/* Completeness */
// "skipDefaultLibCheck": true, /* 忽略库的声明文件的类型检查 */
"skipLibCheck": true /* 忽略所有的声明文件( *.d.ts)的类型检查。 */
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
注释:
- 增量编译:可以提高编译速度(编译时生成一个
tsconfig.tsbuildinfo文件,类似索引的功能,后续编译通过此文件进行增量编译) - 组合模式:编译后的js仍为拆分的js
- 双向协变: https://segmentfault.com/q/1010000040635329/a-1020000040635810 (opens new window)
# 类型
# 基础类型与类型注释
TS只在编译的时候判断类型,在生成的js代码中,是不包含类型判断的。
- Boolen
- Number
- String
- Array 两种写法
- tuple 元组,类似array,长度固定
- Symbol 需要编译为ES6
- bigint 需要编译为ES2020
- Enum 枚举 类似object,且可以根据value取key
- Any 任意值 会导致ts不去判断类型,js执行可能会报错
- Void 空值 表示没有任何类型,例如函数没有返回 或者 赋值 undefined或null
- Null
- Undefined
- Never 永不存在的值,例如函数中永远没有终点的情况
// 类型注释【:】
let boolenVal: boolen = true // var boolenVal = true
let num: number = 123 // var num = 123
let str: string = 'abc' // var str = 'abc'
// 泛型写法
let list1: Array<number> = [1,2,3] // let list1 = [1, 2, 3];
// 默认写法
let list2: number[] = [1,2,3]
// 元组写法:数组长度不变
let list: [number, number, number] = [1,2,3] // var list = [1, 2, 3];
let symbolVal = Symbol(); // 输出的结果和ts写法一致
let obj = {
[symbolVal]: "abc"
}
enum Color {Red = 2, Green, Blue} // 如果值为number 可以自动递增赋值;其他情况必须全部手动赋值。
/*
(function (Color) {
Color[Color["Red"] = 2] = "Red";
Color[Color["Green"] = 3] = "Green";
Color[Color["Blue"] = 4] = "Blue";
})(Color || (Color = {}));
*/
let green_color: Color = Color.Green // let green_color = Color.Green;
let red_color: Color = Color[2] // let red_color = Color[2];
// 函数返回值一般不用定义,ts会自动理解
function a(name: string): void{} // function a(name){}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 类型注释【:】
let boolenVal: boolen = true // var boolenVal = true
let num: number = 123 // var num = 123
let str: string = 'abc' // var str = 'abc'
// 泛型写法
let list1: Array<number> = [1,2,3] // let list1 = [1, 2, 3];
// 默认写法
let list2: number[] = [1,2,3]
// 元组写法:数组长度不变
let list: [number, number, number] = [1,2,3] // var list = [1, 2, 3];
let symbolVal = Symbol(); // 输出的结果和ts写法一致
let obj = {
[symbolVal]: "abc"
}
enum Color {Red = 2, Green, Blue} // 如果值为number 可以自动递增赋值;其他情况必须全部手动赋值。
/*
(function (Color) {
Color[Color["Red"] = 2] = "Red";
Color[Color["Green"] = 3] = "Green";
Color[Color["Blue"] = 4] = "Blue";
})(Color || (Color = {}));
*/
let green_color: Color = Color.Green // let green_color = Color.Green;
let red_color: Color = Color[2] // let red_color = Color[2];
// 函数返回值一般不用定义,ts会自动理解
function a(name: string): void{} // function a(name){}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 联合类型
如果可能存在多个类型需要组合,就可以使用联合类型(union);
使用时需要手动添加判断类型
function aa(id: number | string | boolean){
// console.log(id.toLocaleLowerCase()); // 报错
if(typeof id == "string"){
console.log(id.toLocaleLowerCase());
}else{
console.log(id);
}
}
2
3
4
5
6
7
8
# 类型别名
使用type来定义类型,可以实现类型的复用
type Point = {
x: number,
y: number | string
}
// 不可以添加
// type Point = {} // 会报错
// 可以扩展
type threePoind = Point & {
z: number
}
2
3
4
5
6
7
8
9
10
# 接口
功能、使用和类型别名类似,支持扩展(extend),还支持添加。
需要注意的是:即使是后面添加的参数,在前面先使用的类型中,也需要有对应参数。
interface Point {
x: number,
y: number | string
}
// 可以添加
interface Point {
c: number, // 相当于Point有了三个参数:x,y,c
}
// 可以扩展
interface threePoind extends Point {
z: number
}
2
3
4
5
6
7
8
9
10
11
12
# 类型断言
当语句返回ts不认识当类型时,需要使用类型断言帮助ts识别类型。只能更具体。
// const myCanvas = document.getElementById('my-canvas');
const myCanvas = document.getElementById('my-canvas') as HTMLCanvasElement
// 只能更具体
const x = 'hello' as number // 会报错,因为已经识别为string了
const x = ('hello' as unknown) as number // 可以执行
2
3
4
5
# 文字类型
当需要限制值需要符合条件:属于某一组值时,可以使用文字类型去限制。
function printString(str1: string, str2: 'left' | 'right' | 'center'){}
printString('leo', '11') // 类型“"11"”的参数不能赋给类型“"left" | "right" | "center"”的参数
printString('leo', 'left')
2
3
# 类型缩小
实际上就是js做区分的种种办法。
- typeof 类型守卫:使用typeof判断类型然后做对应操作
- 真值缩小:使用 boolean 类型去判断传入值的 true / false
- 等值缩小:使用 === !=== == != 判断不同值应该如何处理
- in 操作符缩小:value in Type 来判断 Type 中是否有改属性
- instanceof 操作符缩小: 查看是否是XX的实例
- 分配缩小:使用三元运算来隐式的给参数设置类型
function print(strs: string | string[] | null, str1: string | number){
// 为什么null的判断需要放在前面,因为null的类型也是object,放到后面,会导致 for of时 strs可能为null
if(!strs){return} // 真值缩小 先过滤掉null等
// if(strs && typeof strs == 'object'){ // 或者在 object的时候判断null
if(typeof strs == 'object'){ // 类型守卫
for (const str of strs) {
console.log(str);
}
} else if(typeof strs == 'string'){
if(strs === str1){ //等值缩小
}else{
}
} else {
}
}
// in 操作符
type Fish = { swin: ()=> void }
type Bird = { fly: ()=> void }
type Man = { fly?: ()=> void, swin?: ()=> void }
// 增加Man类型之后,ts需要用as来断言 使检查通过,但是不代表js一定不报错
// 假设参数为空对象{}那么ts通过,但js会报错
function move(animal: Fish | Bird | Man){
if("swin" in animal){
return (animal as Fish).swin()
}
return (animal as Bird).fly()
}
// instanceof
function logVal(x: Date | string){
if(x instanceof Date){
console.log(x.toUTCString());
} else {
console.log(x.toString());
}
}
let x = Math.radom() < 0.5 ? 10 : '123' // 分配缩小 => x: number | string
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 控制流分析
假设一个函数可以返回3种类型(现式),在调用函数时,如果由于逻辑操作,使得返回值类型被限制为2种,那么返回值的类型
function getX(){
const a = Math.random()
let x: string | number | boolean = a < 0.5
if(x){
return 'test'
}
return 100
}
let a_x = getX()
a_x = '1'
a_x = 1
a_x = true // 报错:a_x 类型 string | number 不可以赋值 boolean
2
3
4
5
6
7
8
9
10
11
12
# 使用类型谓词
定义一个函数,使返回值是一个类型谓词。
type Fish = {
name: string,
swim: () => void
}
type Bird = {
name: string,
fly: () => void
}
function isFish(pet: Fish | Bird): pet is Fish{ // 类型谓词
return (pet as Fish).swim !== undefined
}
function getSmallPet(): Fish | Bird{
let fish: Fish = {
name: 'sharkey',
swim: ()=>{}
}
let bird: Bird = {
name: 'sparrow',
fly: ()=>{}
}
return Math.random() > 0.5 ? bird : fish
}
let pet = getSmallPet()
if(isFish(pet)){
pet.swim()
}else{
pet.fly()
}
const zoo: (Fish | Bird)[] = [getSmallPet(),getSmallPet(),getSmallPet(),getSmallPet(),getSmallPet()]
const underWater: Fish[] = zoo.filter(isFish)
const underWater2: Fish[] = zoo.filter(isFish) as Fish[]
const underWater3: Fish[] = zoo.filter((pet): pet is Fish=>{
if(pet.name == 'book'){
return false
}
return isFish(pet)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 函数
# 函数类型表达式
let fn: (a: string) => void // void 表示 返回为空
type FunctionA = (a: string) => void
let fnA: FunctionA
2
3
# 调用签名
给函数增加标签来让ts识别函数是某种类型的
type Callback = { // 实际还是一个函数,disc为标记函数的属性
disc: string,
(someArg: number): boolean // 表示函数的入参时number类型,返回值时boolean类型的
}
function doA(fn: Callback){
console.log(fn.disc + ' returen ' + fn(6));
}
function callbackA(n:number){
console.log(n);
return n > 1
}
callbackA.disc = 'hellow' // 给函数赋值标记 让ts识别callback是Callback类型的
doA(callbackA)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 构造签名
在调用函数签名前增加new关键字来实现,表示函数使用时 需要先 new。
class Ctor{
s:string
constructor(s: string){
this.s = s
}
}
type SomeConstructor = {
new (s: string): Ctor // 带new才是构造签名
}
function fnB(ctor: SomeConstructor){
return new ctor('hello') // 使用参数时需要new
}
const fB = fnB(Ctor)
console.log(fB.s);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 泛型函数
泛型就是两个值的类型存在关系,函数中,输入输出值间存在某种关系,用一个字符来表示这个不确定的类型的处理,就是泛型函数。
泛型也可以定义多个,用逗号间隔。
// 这里的 Type 不是固定写法,随便用什么代替都可以
function firstElement<Type>(arr: Type[]): Type | undefined{
return arr[0]
}
// 多个泛型
function map<Input, Output>(arr: Input[], func: () => Output): Output[] {
return arr.map(func)
}
2
3
4
5
6
7
8
# 限制条件
因为泛型表示了不确定的类型,当需要对该类型进行限制的时候,就可以使用extend关键字来进行继承另一种类型。
function longest<Type extends { length: number }>(a: Type, b: Type){
if(a.length >= b.length){
return a
}
return b
}
// 下面的参数里都有length属性,所以都可以通过
const longestArr = longest([1,2], [2,3,4,1])
const longestStr = longest('111', '34423')
const longestObj = longest({a: 1, length: 2}, {a: 1, length: 3})
2
3
4
5
6
7
8
9
10
注意: 当返回值的类型也是泛型时,如果实际代码逻辑的返回值不是泛型而仅包含泛型的限制条件 也是不对的。
# 指定类型参数
泛型是不确定的类型,调用时,可以指定泛型的类型。
function combine<Type>(arr1: Type[], arr2: Type[]): Type[]{
return arr1.concat(arr2)
}
const arr_combine = combine([1,2,3], [3, 4])
const arr_combine_1 = combine([1,2,3], ['3', '4']) // 报错,在类型推断时,第一个参数确定了泛型Type是 array[],而第二个是 string[],得到类型不一致的错误
const arr_combine_2 = combine<string | number>([1,2,3], ['3', '4']) // 调用时确定泛型的实际类型
2
3
4
5
6
类型参数使用准则:
- 尽可能使用类型参数本身,而不是对其进行约束
- 尽可能少得使用类型参数
- 如果类型参数只出现一次,请确认是否需要
# 函数重载
多个重名的函数,没有实现部分的叫做重载函数,否则叫实现函数,这样可以满足函数传递不同的参数
// 重载函数:函数只有这两种传递参数的形式
// 下面代码中 使用时只能传递1/3个参数,如果数量不对,ts也会报错
function makeDate(timestamp: number): Date
function makeDate(m: number, d: number, y: number): Date
// 实现函数
function makeDate(mOrTimestanp: number, d?: number, y?: number): Date { // 这样写是为了兼容重载函数的参数
if(d !== undefined && y !== undefined){
return new Date(y, mOrTimestanp ,d)
}
return new Date(mOrTimestanp)
}
2
3
4
5
6
7
8
9
10
11
# 属性 / 参数
# 可选属性
- 如果遇到不确定是否传递的参数,可以使用 ? 来表示
function a(name?: string): void{
console.log(name?.toString()) // 同理在取值的时候也需要使用?
}
2
3
function a(name?: string): void{
console.log(name?.toString()) // 同理在取值的时候也需要使用?
}
2
3
注意: 回调的函数类型一定不要用可选参数,除非你不打算传递参数。(防止被别人调用时出现异常)
- 如果遇到参数一定不是 undefined 或 null 时,可以使用 ! 来表示
function a(name: number | null): void{
console.log(name!.toFixed()) // 没有!时会提示,当name时null时 没有toFixed函数
}
2
3
function a(name: number | null): void{
console.log(name!.toFixed()) // 没有!时会提示,当name时null时 没有toFixed函数
}
2
3
# 只读属性
属性前增加 readonly 修饰符的,用作只读属性。只读属性只控制当前属性,对象的子属性需要手动修改是否只读。
interface someType{
readonly prop: string,
readonly father: { // 只读
child1: sting // 可读可写
}
}
function doSth(obj: someType){
obj.prop = '1' // 报错,只读属性不可修改
obj.father = { child: ‘123’} // 报错
obj.father.child = '123' // 正确
}
2
3
4
5
6
7
8
9
10
11
# 索引签名
就是给属性名也增加一个类型判断
interface StringArray{
[index: number]: string // [] 创建索引建明
}
const myArray: StringArray = ['a', 'b']
const secondItem = myArray[0]
interface TestString{
readonly [a: string]: boolean // 也可以和只读一起使用
x: boolean
}
const testString: TestString = {
x: true,
['xa']: true,
[1]: true
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 扩展类型
# 接口扩展 与 交叉类型
接口扩展:接口 extends 另一个接口,新的接口带有老接口的全部参数。
interface Person{
name: string,
age: number,
}
interface Student{
work: string,
}
interface Boy extends Person{
sex: 'man',
}
const boy_a: Boy = { // 参数需要复合所有接口的
name: 'lilei',
age: 10,
sex: 'man'
}
interface Boy1 extends Person, Student{ // 可以扩展自多个可接口
sex: 'man',
}
const boy_b: Boy1 = {
name: 'lilei',
age: 10,
sex: 'man',
work: 'read book'
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
交叉类型:功能与 extends 同时扩展自多个接口 一致,只是使用 type 写法; 或者在使用 联合类型(|) 语境时,实现交叉功能。
type Boy2 = Person & Student
区别:仅在于语法环境,功能一致。
# 泛型类型
和泛型函数同理,目的是为了解决不确定参数的真实类型,用泛型临时替代,在调用时,传递真实类型以便于ts理解代码。
如果使用unknown或者any,ts就无法判断类型上是否有某个函数
interface Box<Type>{
con: Type
}
const redBox: Box<number> = { // 相当于把number 传参给interface,这样可以有效减少代码量
con: 123
}
2
3
4
5
6
# 类型创建类型
# 泛型
类似于函数的语法来传递类型,即通过类型创建类型。
除了上面讲到的 泛型函数、泛型参数、泛型约束,还有泛型接口可以使用:
// 使用泛型
function identity<Type>(arg: Type): Type{
return arg
}
let identity_a: <Type>(arg: Type) => Type = identity
let identity_b: <Input>(arg: Input) => Input = identity
let identity_c: { <Type>(arr: Type): Type } = identity
// 泛型接口
interface GenericIdentity {
<Type>(arg: Type): Type
}
let my_identity_a: GenericIdentity = identity
// 也可以把泛型写在接口后面,调用时传递进来
interface GenericIdentityFn<Type> {
(arg: Type): Type
}
let my_identity_b: GenericIdentityFn<string> = identity
// 泛型类
class GenericNumber<Type> {
val1: Type
constructor(opt: {
val1: Type
}){
this.val1 = opt.val1
}
}
let myGenericNumber = new GenericNumber({val1: 1})
// 泛型约束 extends
interface LengthWise {
length: number
}
function identitylog<Type extends LengthWise>(arg: Type): Type{
console.log(arg.length);
return arg
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
泛型的 extends 关键字只能解决类型与泛型之间的关系,但是还不够,如果需要在泛型中使用类型的参数,就需要使用 keyof 关键字:
// 泛型约束中使用类型参数
function getProp<Type, Key extends keyof Type>(obj: Type, key: Key){ // 表示 Key 的可选值需要属于 Type 的属性
return obj[key]
}
let obj_a = { a: 1, b: 2, c: 3 }
getProp(obj_a, 'a') // 正确
// getProp(obj_a, 'd') // 类型“"d"”的参数不能赋给类型“"a" | "b" | "c"”的参数
2
3
4
5
6
7
泛型中还可以使用类类型来约束:
// 泛型中使用类类型
// 没有约束
// function create<Type>(c: { new (): Type }): Type{
// return new c()
// }
class BeeKeeper{
hasMask: boolean = true
}
class ZooKeeper{
nameTag: string = 'leo'
}
class Animal{
numLegs: number = 4
}
class Bee extends Animal{
keeper: BeeKeeper = new BeeKeeper()
}
class Lion extends Animal{
keeper: ZooKeeper = new ZooKeeper()
}
// 添加约束 Animal
function createInstance<A extends Animal>(c: { new (): A }): A{
return new c()
}
createInstance(Lion).keeper.nameTag
createInstance(Bee).keeper.hasMask
createInstance(Animal).numLegs
// createInstance(BeeKeeper) //类型“typeof BeeKeeper”的参数不能赋给类型“new () => Animal”的参数。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# keyof操作符
上面【泛型约束中使用类型参数】中有提到过,实际上 keyof 操作符是吧对象属性取出,然后作为联合类型使用。
type TestPoint = {
x: number,
y: number
}
type Position = keyof TestPoint // 相当于 type Position = 'x' | 'y'
2
3
4
5
# typeof操作符
和js中的一致,但是通过组合其他操作符可以表达跟多情景
type Predicate = (x: unknown) => boolean
type K = ReturnType<Predicate> // 预定义K类型是 Predicate 的这个函数类型
function f(){
return 1
}
type P = ReturnType<typeof f> // 因为f是函数 所以去函数的类型 赋给 P
2
3
4
5
6
7
# 索引访问类型
同js ,可以通过索引访问类型某种属性的类型
type Person1 = {
age: number,
name: string
}
type a = Person1['age']
let persona: a = '' // 不能将类型“string”分配给类型“number”
type b = Person1['age' | 'name'] // 也可以使用 | 关键字表示联合类型
let personb: b = true // 不能将类型“boolean”分配给类型“b”
type c = Person1[typeof Person1] // 也可以使用 typeof 关键字表示所有属性值
2
3
4
5
6
7
8
9
# 条件类型
SomeType extends OtherType ? TrueType : FalseType
类似于js中的三元表达式,功能含义也一致,只是参数换成了类型
// 使用重载比较麻烦
interface IdLabel { id: number }
interface NameLabel { name: string }
function createLabel(id: number): IdLabel
function createLabel(name: string): NameLabel
function createLabel(nameOrId: number | string): IdLabel | NameLabel{
if(typeof nameOrId == 'number') {
return {id: nameOrId}
}
return {name: nameOrId}
}
type NameOrId<T> = T extends number ? IdLabel : NameLabel
function createLabel1<T extends number | string>(nameOrId: T): NameOrId<T>{
if(typeof nameOrId == 'number') {
// return {id: nameOrId} // TODO 这里没搞明白为啥返回的与实际类型时相符的,但是这里报错
}
// return {name: nameOrId}
throw ''
}
let createLabelA = createLabel1('aa')
// let createLabelB = createLabel(true) // wrong
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 类
# 使用类
类的基本使用方法与es6一致,只是增加了类型限制,具体使用如下:
class PointX{
x: number = 0 // 初始化:可以直接赋值
y: number
readonly z: string // 只读属性
public a: number = 0 // 默认 公开属性
protected b: number = 0 // 能在当前类和子类中访问
private c: number = 0 // 私有 只能在当前类中访问
static d: number = 100 // 静态成员 编译后 生成 PointX.d = 100;
private static e: number = 100 // 静态成员 可以和上面的一起使用
static #f: number = 10 // 私有的静态成员,不能通过类调用 // 生成 _PointX_f = { value: 10 };
_length: number = 0
constructor(val: string = '1'){ // 初始化:构造函数
this.y = 0
this.z = val // 只读属性在构造函数中可以初始化
}
log(n: number): void{ // 和普通函数一样
// this.z = '22' // 无法分配到 "z" ,因为它是只读属性。
}
// getter / setter
get length(): number{ // number可以不写 能自动推断出
return this._length
}
set length(val){ // 如果属性没set 会推断为只读属性
this._length = val
}
}
const pt = new PointX('a') // 构造函数中需要传递参数,实例化的时候就必须按照规则传参
pt.x = 1
pt.y = 2
// pt.z = '123' // 无法分配到 "z" ,因为它是只读属性。
const pt1 = new PointX() // constructor中设置了默认值时 也可不填参数
// 继承
class PointTest extends PointX{
constructor(){
// 派生类的构造函数必须包含 "super" 调用。
super()
}
// 索引签名, 和上面的索引签名一致,用来规范对象中的参数、函数等
[s: string]: number | string | ((s: number) => void)
a: number = 1
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 类的继承
implements 子句,用来检查类与接口的类型 是否规则兼容(不一定一致,只要能兼容就可以)
interface Pingable{
ping(): void
}
// implements 用来检查 类 与 接口
class Sonar implements Pingable{
// class Sonar implements Pingable, Pongable{ // 支持多个
ping(): void {
console.log(1);
}
}
2
3
4
5
6
7
8
9
10
extends 子句,用来继承,时类与类之间的关系,与es6一致
// 和上面的一致
class PointTest extends PointX{
constructor(){
// 派生类的构造函数必须包含 "super" 调用。
super()
}
log(){
// 因为重名 于是 重写方法
super.log() // 还可以调用 基类 的方法
}
}
2
3
4
5
6
7
8
9
10
11
注意:默认初始化顺序是 基类字段初始化 => 基类函数执行 => 派生类字段初始化 => 派生类函数执行
# 使用泛型
类同样支持泛型 用法一致
class Box<Type>{
contents: Type
// static a: Type // error 静态成员不能引用类类型参数。
constructor(val: Type){
this.contents = val
}
}
const b = new Box<string>('hello')
2
3
4
5
6
7
8
# this类型
this类型指向的是调用函数所属的原型的类型
// this类型
class BoxA {
cont: string = ''
sameAs(oth: this){ // 这里oth指向boxa 但是this指向的是derviedboxa,解析:
// 因为derviedboxa类型是 DerivedBoxA 而boxa上没没有类型DerivedBoxA包含的参数othCont,所以报错
return oth.cont === this.cont // 还没到这一步
}
}
class DerivedBoxA extends BoxA{
othCont: string = ''
}
const boxa = new BoxA()
const derviedboxa = new DerivedBoxA()
derviedboxa.sameAs(boxa) // error // 类型“BoxA”的参数不能赋给类型“DerivedBoxA”的参数。 // 类型 "BoxA" 中缺少属性 "othCont",但类型 "DerivedBoxA" 中需要该属性。
2
3
4
5
6
7
8
9
10
11
12
13
14
# 基于类型守卫的this
this is Type // Type是任意值, this is是固定写法
守卫和if结合起来,可以精细的让ts识别变量的类。
class FileSys{
isFile(): this is FileRep {
return this instanceof FileRep
}
isDirectory(): this is Directory{
return this instanceof Directory
}
isNetworked(): this is Networked & this{
return this.networked
}
constructor(public path: string, private networked: boolean){}
}
class FileRep extends FileSys{
constructor(path: string, public content: string){
super(path, false)
}
}
class Directory extends FileSys{
children: FileSys[]
constructor(path: string, public content: string){
super(path, false)
this.children = []
}
}
class Networked{
host: string = ""
}
const fso: FileSys = new FileRep('/document/test.html', '<html>')
if(fso.isFile()){ // 根据方法的return 获得this is后面的类,来告诉ts 当前的参数是XX类的,可以使用这个类的方法
console.log(fso.content);
}else if(fso.isDirectory()){
console.log(fso.children);
}else if(fso.isNetworked()){
console.log(fso.host);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 参数属性
类定义参数的时候,也可以直接在构造函数中使用 public 等关键字,使得ts可以识别实例化对象中存在该属性
class Params{
constructor(public x: number){ // pubic
}
}
const p = new Params(1)
console.log(p.x);
2
3
4
5
6
7
# 类表达式
可以写匿名类,然后赋值后使用, 和直接定义class 功能一致
const someClass = class<Type> { // 匿名类
cont: Type
constructor(val: Type){
this.cont = val
}
}
const someClassA = new someClass('hello')
2
3
4
5
6
7
# 抽象类和成员
abstract 关键字,实现功能:创造一个不可以被实例化的类,只能作为基类去继承。
abstract class Base { // 抽象类
abstract getVal(): string // 抽象方法需要在 子类中定义同名函数,需要复合基类的类型规则
logVal(){
console.log(this.getVal())
}
}
2
3
4
5
6
# 模块
# ES语法
ES模块使用语法 与js保持一致
// ts-test/utils/regexp.ts
export default {
isPhone(phone_num: string, country_code?: string): boolean{
let regexp: RegExp | null = null
switch(country_code){
default:
case 'cn': regexp = /^((13\d)|(14[5,7,9])|(15[0-3,5-9])|(166)|(17[0,1,3,5,7,8])|(18[0-9])|(19[8,9]))\d{8}/; break;
}
return regexp.test(phone_num)
}
}
export let test = '100'
export type AA = {
val: 'aa'
}
// ts-test/02.ts
import RegExpTools, {test as TestVal} from './utils/regexp'
// import AA from './utils/regexp' // 也可以定义一个type/interface 然后又导出
// import type AA from './utils/regexp' // 加上type 也可以
function checkPhone(str: string){
return RegExpTools.isPhone(str)
}
checkPhone(TestVal)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 总结
TS 的入门课程已经看完,后面有时间可以想一想怎么在老代码上比较便捷的增加ts