TypeScript基础
xdf TypeScript
介绍
TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
1、TypeScript 是基于JavaScript的一个扩展语言
2、TypeScript 包含了JavaScript所有内容,即:TypeScript是JavaScript 的一个超集
3、TypeScript 增加了:静态类型检查、接口、泛型等
4、TypeScript 需要编译为JavaScript,然后交给浏览器或其它JavaScript运行环境执行
# ts编译
1、命令编译(了解)
1-1:
npm i typescript -g // 安装后全局就会有一个 tsc 命令(TypeScript Compiler)
1-2:
// tsc 命令将ts文件变成js文件
tsc index.ts
2、自动化编译(练习推荐)
2-1:
tsc --init // 生成了一个ts的配置文件 tsconfig.json
2-2:
tsc --watch // 监听整个工程ts文件
注:开发时其实不用1和2: 不管vue 还是 react,官方都是提供 webpack或vite 脚手架配置来编译
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 类型总览
ts 包含 js的所有类型 string、number、boolean、undefined、null、symbol、bigint、Object
ts在js的类型基础上增加了 6 中新的类型(any、unknown、never、void、tuple、enum) + 自定义类型方式 (type、 interface)
- string
let a: string // a只能存string类型
a = 'hello' // 正确
a = 1 // 错误
// 备注: 如果变量的声明和赋值是同时进行的,TS可以自动对变量进行类型的检测
let b = 10
b = 'hello' // 报错, 此时 let = 10 <===> let b: number = 10
1
2
3
4
5
6
7
2
3
4
5
6
7
- number
let a: number // a只能存number类型
a = 1 // 正确
a = 'hello' // 错误
// 接受参数是 number 类型, 返回值是number类型, 不能是其它类型
function count(x:number, y:number):number {
return x+y
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- any
含义:任意类型,一旦将变量类型限制为any,那就意味着 放弃了 对改变量的类型检查
let a: any
a = 10
a = 'b'
// 都不会报错
// 注意点:any类型的变量,可以赋值给任意类型的变量
let c: any
c = 9
let x: string
x = c // 不会报错
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- unknown
含义:未知类型,可以理解为一个类型安全的any,适用于:不确定数据的具体类型
// 白话:当时不知道是什么类型,以后有可能会知道
let a: unknown
a = 99
a = false
a = 'hello'
let x: string
x = a // 报错 unknown 不能赋值给string类型
// 解决办法1
if (typeof a === 'string') { // 不会报错
x = a
}
// 解决办法2 (断言)
x = a as <string>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- never
含义:任何值都不是,简言之就是不能有值,undefined、null、''、0 都不行
// never 不是限制变量没有意义,可以用于限制函数返回值
let a: never
a = 9 // 报错
function demo():never{ // 报错, demo会默认返回undefined
}
function demo():never{ // 不会报错
throw new Error('程序异常')
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- void
含义:空,void通常用于函数返回值,表示函数不返回任何值(调用者不应该依赖返回值,也不能用返回有任何操作)
function demo():void{ // 不报错,因为有隐式返回值 undefined
}
function demo():void{ // 报错
return '' // null 0 等都报错
}
// 总结: void只能接受一种空 undefined,从语义上讲函数调用者不应该关心函数的返回值,也不应该依赖返回值进行任何操作,即使返回了undefined
1
2
3
4
5
6
7
2
3
4
5
6
7
- object
结论:实际开发中用的相对较少,因为范围太大了
let a: object // a能存储的类型是【非原始类型】
let b: Object // 大写Object b能存储的类型是可以调用到Object方法的类型
// 下面都支持
a = {}
a = {name: 'tom'}
a = [1,2,3,4]
a = function() {}
a = new String('123')
class Person {}
a = new Person()
// 以下代码,是将[原始类型]赋给a,有警告
a = 1 // 警告:不能将类型“number”分配给类型object
a = true // 警告:不能将类型“boolean”分配给类型object
a = '您好' // 警告:不能将类型“string”分配给类型object
// 以下不报错
b = {}
b = {name: 'tom'}
b = [1,2,3,4]
ba = function() {}
b = new String('123')
class Person {}
b = new Person()
// 因为能调用到Object上的方法,如:let c = 100 c.toString()
b = 1
b = true
b = '您好'
// 以下报错,因为null调用不掉Object身上的方法
b = null
b = undefined
// 声明对象类型 ?代表可选属性 可写可不写
let person = { name: string, age?: number} // person写法: 里面可以不用,但是必须要换行
// 不能少写属性,且类型必须符合
person = { name: 'tome', age: 18 }
// 索引签名
let person = {
name: string
age?: number
[key: string]: any // 除了name是必须写的,可以有任何一个key,key是string类型,key对应的值是任何类型
}
person = { name: 'tome', age: 18, gender: '男' } // 可以在gender继续追加属性
// 声明函数类型 ts 中 => 表示函数类型,不是箭头函数
let count: (a: number, b: number) => number // count必须满足此格式的函数 参数是number类型,返回值是number类型
// 形参,名字可能不一样a和b
count = function(x, y) {
return x + y
}
// 声明数组类型 两种写法
let arr: string[] // 数组里面的每一个值必须是string类型
arr = ['1', '2']
let arr2: Array<number> // 泛型
arr2 = [100, 200]
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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
- tuple
元组 结论:是一种特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型是已知的且可以不同。元组用于精准描述一组值的类型,?表示可选元素
tuple 不是关键字
let arr1: [string, number] // 第一元素必须是 string 类型,第二个元素必须是 number 类型
arr1 = ['hello', 10]
let arr2: [string, number?] // 第一元素必须是 string 类型,第二个元素是可选的 如何存在 必须是 number 类型
arr2 = ['hello'] || arr2 = ['hello', 1]
let arr3: [string, ...number[]] // 第一元素必须是 string 类型,后面的元素可以是任意数量的 number 类型
arr3 = ['hello', 1, 2, 3] // 可以无限多个number,也可以不写
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- enum
枚举(enum)含义:可以定义一组命名常量,它能增强代码的可读性,也让代码更好维护
enum Direction {
Up,
Down,
Left,
Right
}
console.log(Direction) // {0: 'Up', 1: 'Down', 2: 'Left', 3: 'Right', Up: 0, Down: 1, Left: 2, Right: 3}
console.log(Direction.Up) // 输出0
console.log(Direction[0]) // 输出Up
function walk(data:Direction) {
// console.log(data) // 输出 0
if (data == Direction.Up) { // 具有反向映射
console.log('向上走')
} else if (data === Direction.Down) {
console.log('向下走')
} else if (data === Direction.Left) {
console.log('向左走')
} else if (data === Direction.Right) {
console.log('向右走')
} else {
console.log('位置错误')
}
}
walk('up') // 报错
walk(Direction.Up)
// Direction.Up = 1 // 都是只读的属性,不能再次修改
/*
数字枚举 - 有反向映射 {5: 'Up', 10: 'Down', 20: 'Left', 30: 'Right', Up: 5, Down: 10, Left: 20, Right: 30}
enum Direction {
Up = 5,
Down = 10,
Left = 20,
Right = 30
}
字符枚举 - 无反向映射 {Up: 'up', Down: 'down', Left: 'left', Right: 'right'}
enum Direction {
Up = 'up',
Down = 'down',
Left = 'left',
Right = 'right'
}
// 常量枚举 在编译时会被你练,避免生成一些额外的代码
const enum Direction {
Up = 'up',
Down = 'down',
Left = 'left',
Right = 'right'
}
console.log(Direction.Up) // ts 编译成js 只有 编译 Up = 'up'这一行 编译如下
"use strict"
let x = 0 /* Direction.up */
*/
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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
- type
含义:可以为任意类型创建别名,让代码更简洁、可读性强、同时能更方便地进行类型复用和扩展
// 基本用法 (类型别名)
type num = number
let a: num
a = 100
// 联合类型
type Status = number | string // 可以是 数字 或 字符串
function printStatus(data:Status):void {
console.log(dta)
}
printStatus(404)
printStatus('404')
type Gender = '男' | '女' // 可以是 男 或 女
function printStatus(data:Gender):void {
console.log(dta)
}
printStatus('男')
printStatus('女')
// 交叉类型
type Area = {
height: number;
width: number;
}
let a: Area // a 只能存储对象,对象里面必须有 height 和 width 且值的类型是 数字
a = {
height: 100,
width: 100
}
type Address = {
num: number, // 楼号
cell: number // 单元号
}
type House = Area & Address
const house:House = { // 必须有所有字段和对应类型值
height: 100,
width: 100,
num: 10,
cell: 1
}
/***** 特殊情况 ******/
// 官网 https://www.typescriptlang.org/docs/handbook/2/functions.html#assignability-of-functions
type LogFunc = () => void
const f1:LogFunc = () => {
return 100 || [] || '' // 不会报错,按道理 void 只接受undefined
}
// 当使用类型声明限制函数返回值为void时,ts 并不会严格要求函数返回空
// 为什么??????? 是为了确保如下代码成立
const src = [1,2,3]
const dst = [0]
src.forEach((el) = > {
dst.push(el) // forEach 不需要用 return, 这样会默认返回 undefined
})
src.forEach((el) = > dst.push(el)) // 这样不行 因为 简写 push 会默认返回 数组长度,不是undefined
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
- class类 属性修饰符、抽象类
public:含义:公开的 规则:可以 被类内部、子类、类外部 访问
protected: 含义:受保护的 规则:可以被 类内部、子类 访问
private: 含义 私有的 规则:可以被 类把内部访问
readonly:含义:只读属性 规则:属性无法修改
// 抽象类
// abstract 含义:抽象类不能实例化,其意义可以被继承,抽象类可以有普通方法,也可以有抽象方法
abstract class Package{
// 构造方法
constructor(public weight: number) {}
// 抽象方法
abstract calculate(): number
// 具体方法
printPackage() {
console.log(`包裹重量为:${this.weight}kg,运费为:${this.calculate()}元`)
}
}
class StandardPackage extends Package {
// unitPrice: number
constructor( // 简写模式, 不然两个注释需要放开
weight: number,
public unitPrice: number
) {
super(weight)
// this.unitPrice = unitPrice
}
calculate(): number { // 必须要实现calculate, 不实现报错
return this.weight * this.unitPrice
}
}
const s1 = new StandardPackage(10, 5)
s1.printPackage()
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
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
- interface 接口
接口(interface)含义:interface 是一种定义结构的方式,主要作用是为:类、对象、函数等规定一种契约,这样可以确保代码的一致性和类型安全,但要注意interface只能定义格式,不能包含任何实现
// 定义 类 结构
interface PersonInterface{
name: string
age: number
speak(n: number): void // 定义一个方法 接受一个number类型的参数,没有返回值
}
class Person implements PersonInterface{ // implements: 实现
constructor(
public name: string,
public age: number
)
speak(n: number): void {
for (let i = 0; i < n; i++) {
console.log(`您好,我教${this.name}, 我的年龄是${this.age}`)
}
}
}
const p1 = new Person('tom', 18)
p1.speak(4)
// 定义 对象 结构
interface UserInterface{
name: string
readonly: gender: string // 只读属性
age?: number, // 可选属性
run: (n: number) => void
}
const user: UserInterface = {
name: '张三',
gender: '男',
age?: 18,
run(n) {
console.log(`奔跑了${n}米`)
}
}
// 含义 函数 结构
interface CountInterface {
(a: number, b: number): number
}
const count: CountInterface = (x, y) =>{
return x + y
}
// 接口之间继承
interface PersonInterface {
name: string
age: number
}
interface StudentInterface extends PersonInterface{
grade: string
}
const stu: StudentInterface = { // 必须满足三个属性
name: '张三',
age: 18,
grade: '初二'
}
// 接口自动合并(可重复定义)
interface PersonInterface1 {
name: string
age: number
}
interface PersonInterface1 { // 用1区分 定义两个PersonInterface1不会报错,有可合并性
gender: string
}
const p:PersonInterface1 = {
name: 'tome',
age: 18,
gender: '二年级'
}
// 总结:如何使用接口?
// 1、定义对象格式:描述数据模型 API响应格式、配置对象等等,是开发中用的最多的场景。
// 2、类的契约:规定一个类需要实现那些属性和方法
// 3、自定合并:一般用于扩展第三方库的类型
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
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
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
- interface和type的区别
// 相同点:interface 和 type 都可以用于定义**对象结构**,两者在许多场景中是可以互换的。
// 不同点:
// interface:更专注于定义**对象**和**类**的结构,支持继承、合并
// type:可以定义类型别名、联合类型、交叉类型,但不支持继承和合并
1
2
3
4
2
3
4
- interface和抽象类的区别
// 相同点:都用于定义一个类的格式(应该都遵循的契约)
// 不同点:
// interface:只能描述结构,不能有任务实现代码,一个类可以实现多个接口
// 抽象类(abstract):既可以包含抽象方法,也可以包含具体方法,一个类只能继承一个抽象类
// 一个类可以实现多个接口
interface FlyInterface {
fly(): void
}
interface SwimInterface {
swim(): void
}
class Duck implements FlyInterface, SwimInterface { // implements实现两个接口
fly(): void {}
swim(): void {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 泛型
含义:泛型允许我在定义函数、类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时才被指定具体的类型,泛型能让同一段代码适用于多种类型,同时仍然保持类型的安全性。
// 泛型函数
function logData<T>(data:T) { // T 可以是任何字母或单词
console.log(data)
}
logData<number>(100) // <T> 想当于number
logData<string>('100') // <T> 想当于string
// 泛型可以传多个
function logData<T,U>(data1:T, data2: U): T | U { // T | U 联合类型
Date.now() % 2 ? console.log(data1) : console.log(data2)
}
logData<number, boolean>(100, true)
// 泛型接口
interface PersonInterface<T> {
name: string
age: number
extraInfo: T
}
type JobInfo = {
title: string
company: string
}
let p:PersonInterface<number> = { // <number> 表示:将类型参数 T 替换为 number
name: 'tom',
age: 18,
extraInfo: 100
}
let p2:PersonInterface<JobInfo> = { // JobInfo传递的泛型
name: 'tom',
age: 18,
extraInfo: {
title: '前端开发',
company: '开发公司'
}
}
// 泛型类
class Person<T> {
constructor(
public name: string,
public age: number,
public extraInfo: T
) {}
speak() {
console.log(this.extraInfo)
}
}
const p1 = new Person<number>('tom', 18, 200)
const p2 = new Person<JobInfo>('tom', 18, {
title: '前端开发',
company: '开发公司'
})
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
- tsconfig.json配置部分讲解
tsconfig.json 是ts编译器的配置文件,ts编译器可以根据他的信息来对代码进行编译。
{
/*
include: 用来指定那些ts文件需要编译
路径:** 表示任意目录 * 表示任意文件
*/
"include": {
".src/**/*"
},
/*
exclude: 不需要被编译的文件
默认值:["node_modules", "bower_components", "jspm_packages"]
*/
"exclude": {
".src/hello/**/*"
},
/*
extends: 被继承的配置文件
含义:当前文件(如tsconfig.json)中会自动包含config目录下base.json中的所有配置信息
*/
"extends": "./config/base",
/*
files: 指定被编译文件的列表,只有需要编译的文件少时才会用到
*/
"files": "",
/*
compilerOptions: 编译器选项
*/
"compilerOptions": {
// target 用来指定ts被编译为的ES的版本
"target": "ES6",
// module 指定要使用的模块化的规范
"module": "es2015", // commonjs
// lib 用来指定项目中要使用的库
"lib": [], // 一般很少用 如果在node环境使用,想用dom,那就写 ['DOM', 'ES6']
// outDir 用来指定编译后文件(js文件)所在的目录
"outDir": "./dist", // 相当于编译后的js文件都放在了dist目录下
// 将代码合并为一个文件
// 注意:如何代码中用了模块化,将无法合并,如何还想合并需要将 "module"属性值设置为 "system" 或 "amd"
"outFile": "./dist/app.js", // 相当于将编译后的js文件合并到同一个文件中,dist目录的aap.js文件里面
// 是否对src目录(因为exclude设置src)的js进行编译,默认是false
"allowJs": false, // 设置 true 就会把 js文件编译到了dist目录下
// 是否检查js代码是否符合预发规范,默认是false
"checkJs": false,
// 是否移除注释
"removeComments": true,
// 不生成编译后的文件 默认值是 false
"noEmit": true, // 相当于dist里面不会生成任何的js
// 当有错误不生成编译后的文件
"noEmitOnError": true,
// 所有严格检查的总开关
"strict": true, // 相当于下面4个属性默认全开
// 用来设置编译后的文件是否使用严格模式, 默认是 false
"alwaysStrict": true, // 当代码里面用于了模块化,默认就是严格模式
// 不允许隐式的any类型
"noImplicitAny": true,
// 不允许不明确类型的this
"noImplicitThis": true,
// 严格的检查控制
"strictNullChecks": true,
}
}
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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