当前位置:   article > 正文

TypeScript学习总结(一)_ts 引用外部枚举

ts 引用外部枚举

TypeScript学习总结(一)

在这里插入图片描述

1、阅读本文章,需要具备的知识技能:

  • JavaScript
  • node.js基础

2、阅读本文章,需要安装的环境和工具:

一、什么是TypeScript?

TypeScript是JavaScript类型的超集,它可以编译成JavaScript代码。

TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。

TypeScript是JavaScript类型的超集的意思是,TypeScript在ES6基础上做了升级,而ES6是基于ES5进行的升级,这是TypeScript是JavaScript超级的理解。

在这里插入图片描述

二、为什么要学习TypeScript?

1、TypeScript对比JS

TypeScript属于静态类型的编程语言,JS属于动态类型的编程语言。

  • 静态类型:编译期做类型检查
  • 动态类型:执行期做类型检查
  • 代码先编译后执行(执行编译后的代码)

TS在代码编译的时候(代码执行前)就可以发现错误(早)

JS在代码执行的时候(编译后执行时)才能发现错误(晚)

2、TS相比JS的优势

1.更早(写代码的同时)发现错误,减少找Bug、改Bug时间,提升开发效率。

2.程序中任何位置的代码都有代码提示,随时随地的安全感,增强了开发体验。

3.强大的类型系统提升了代码的可维护性,使得重构代码更加容易。

4.支持最新的 ECMAScript语法,优先体验最新的语法,让你走在前端技术的最前沿。

5.TS类型推断机制,不需要在代码中的每个地方都显示标注类型,让你在享受优势的同时,尽量降低了成本。

三、TypeScript基础知识

1、TypeScript的安装和编译

1.1 通过 typescript 包来编译.ts文件

安装命令: npm i -g typescript

typescript包:用来编译TS代码的包,提供了tsc命令,实现了TS-> JS的转化。

验证是否安装成功:tsc-V(查看 typescript的版本)。

使用方法:

  • **将TS代码通过tsc命令编译成JS代码,**在终端中执行命令:tsc code.ts(此时在同级目录中会出现一个同名的code.js文件)
  • 执行JS代码,在终端中执行命令:node code.js

在这里插入图片描述

1.2 通过 ts-node包来编译.ts文件

上面的执行命令比较繁琐,提供一个比较简单的命令

**安装命令: **npm i -g ts-node

ts-node 包在内部已经将TS转化 JS,然后再运行JS代码

使用方法ts-node 文件名称,例如:ts-node code.ts

1.3 通过 esno包来编译.ts文件

上面的执行命令比较繁琐,提供一个比较简单的命令

**安装命令: **npm i -g esno

esno 内部使用了esbuild 来加载 TypeScript 和 ESM的Node.js运行时

使用方法esno 文件名称,例如:esno code.ts

1.4 通过 ts-node-dev包来编译.ts文件【推荐】

上面通过esnots-node执行命令,如果更改了代码,esnots-node不支持自动监听ts文件变化,自动重新执行程序,而ts-node-dv包就可以实现自动监听文件的变化然后自动重新执行最新的程序,节省了重新启动命令的时间,更加方便。

**安装命令: **npm i -g ts-node-dev

运行ts-node-dev命令,还有一个简短的别名命令tsnd,可以在终端中输入 tsnd 检查下是否安装成功。如果出现如下图所示内容,说明安装成功了。

在这里插入图片描述

使用方法tsnd --respawn 文件名称,例如:tsnd --respawn code.ts,在文件中,更改代码后,会自动执行更改后的代码运行结果。

四、TypeScript中的类型

JS有类型,但是JS不会检查变量的类型是否发生变化,而TS会检查

1、TypeScript基础类型

TypeScript支持与JavaScript几乎相同的数据类型。

以下TS代码,放到一个ts文件中,并通过 tsnd --respawn 文件名称,例如:tsnd --respawn code.ts 方式进行执行。

1.1五中常见的基础类型(原始类型)
// : string 为类型注解,表示变量name1为字符串类型。
// 在TS中,一旦为变量添加了类型注解,这个变量的类型就不能发生改变,否则编译就会报错。
let name1: string = 'xiaozhu'; // 字符串类型
let name2: string = `xiaozhu`; // 模版字符串
let age: number = 18; // 数值类型
let cool: boolean = true;  // 布尔值
let u: undefined = undefined; // Undefined
let n: null = null; // Null

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
1.2 数组
// TypeScript像JavaScript一样可以操作数组元素。有两种方式可以定义数组。 
// 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组:
let arr1: number[] = [1, 2, 3]; // 数组
// 第二种方式是使用数组泛型,Array<元素类型>:
let list: Array<number> = [1, 2, 3];

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
1.3 联合类型

// 有小括号,表示首先是数组,然后数组中可以出现number和string类型的元素
let arr2: (number | string)[] = [1,3,'a','c']
let arr3: (number | string)[] = [1,3]
let arr4: (number | string)[] = ['a','b']
// 没有小括号,既可以是number 又可以是 string数组(数组中的元素必须是string类型)
let arr5: number | string[] = ['a','b','3']
let arr6: number | string[] = 123


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
1.3 元组(Tuple)

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。

let array7: number[] = [1,2,3,5]; // 有局限性

// 使用元组,当能够确切的知道包含都少个元素,以及特定索引对应的类型时,需要使用元组
let position1: [ number,number ] = [12, 13] // OK,数组中第一个和第二个都是数值
let position2: [ number,string ] = [12, '13'] // OK ,数组中第一个是数值、第二个字符串
let position3: [ number,string ] = ['12', 12] // Error,跟元组类型对应类型不一致
let position4: [ number,string ] = [12,'13',14] // Error,跟元组类型对应类型以及元素个数不一致。


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

但使用元组确实能帮助我们进一步提升数组结构的严谨性

2、TypeScript 特殊类型

2.1 any

any 指的是一个任意类型,如果我们想要为那些在编程时不太清楚的类型变量指定一个类型时,可以使用any类型来标记这些变量。但是也是由于any提供了这样的“便利性”,我们可以对任何为any的类型的变量进行任何操作,即使是在获取该类型的变量中没有的属性或者方法,也不会报任何错误,如下代码所示:

let everybody: any = []
everybody.name = '筱竹' // 此处不会提示报错
everybody.age = 18 // 此处不会提示报错
everybody.sayHi() //  此处不会提示报错
let age: number = everybody //  此处不会提示报错
let name: string = everybody //  此处不会提示报错
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

根据上述的"错误演示",给变量设置any类型不推荐使用(不推荐,不建议使用any类型),因为如果在TS代码中很多变量都是any类型,此时TS的静态类型的检查(静态类型:编译期做类型检查)不起任何的作用(any会绕过TS的类型检查),跟没有用TS是一样的,所以原则上不推荐使用any类型。

2.2 void

void 指的是没有任何类型,某种程度上来说,void类型像是与any类型相反, void 用于描述一个内部没有 return 语句,或者没有显式 return 一个值的函数的返回值,我们可以认为 void 表示一个空类型,如:

// 如果函数没有返回值,那么函数的返回值类型 为 void(空)
 function handle(name: string): void {
  console.log('您好',name)
}
handle('张三') // 您好 张三

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
2.2 never

never 指的是永远不存在的值的类型, 例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型;

// error函数的类型因为是never,永远不会有返回值,所以它的返回值类型就是 never。
function error(msg: string): never {
  throw new Error(msg) // [ERROR]  Error: 张三
}
error('张三') 


// infiniteLoop函数体中的代码是一个死循环,那么这个函数的返回值类型也是 never
function infiniteLoop(): never {
  while (true) {}
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

never类型可以是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never,如下代码所示:

// 没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never,
let MyAge: never = 18 // 提示:不能将类型“number”分配给类型“never”。
let MyName: never = 'string' // 提示:不能将类型“string”分配给类型“never”。
let MyCool: never = true // 提示:不能将类型“boolean”分配给类型“never”。

// never类型是任何类型的子类型,也可以赋值给任何类型,如下所示:
let Age: number = MyAge // never类型可以赋值给 number类型
let Name: string = MyName // never类型可以赋值给任何类型
let Cool: boolean = MyCool // never类型可以赋值给任何类型
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在这里插入图片描述

2.3 unknown

unknown 是 TypeScript 3.0 版本中新添加的一个顶级类型,用来描述类型并不确定的变量, 对照于anyunknown是类型安全的(因为any会绕过TS类型检查,而unknown会进行类型的检查,所以unknown是类型安全的,当我们不知道是什么类型的时候,可以用unknown替代any), 任何值都可以赋给unknown,但 unknown 类型的值只能赋值给 unknownany

let userName: unknown = '筱竹'
let userAge: unknown = 18
let userBool: unknown = true

let info: unknown
let typeUnknown: unknown = info // unknown 类型的值只可以赋值给 unknown 或 any
let everything: any = info //  unknown 类型的值只可以赋值给 unknown 或 any
let num: number = info // 不能将类型“unknown”分配给类型“number”。ts(2322)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在这里插入图片描述

2、TypeScript常用类型(一)

2.1 函数类型

函数类型包含两部分:参数类型和返回值类型。

给函数类型,单独指定参数类型和返回值类型,代码如下:

// x: number, y: number 为参数类型,():number为返回值类型
function add(x: number, y: number): number {
  return x + y
}
console.log('add', add(1, 2)) // add 3

let myAdd = (x: number, y: number): number => {
  return x + y
}
console.log('myAdd', myAdd(2, 3)) // myAdd 5

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

给函数类型,同时指定参数类型和返回值类型,代码如下:

// (x: number, y: number) => number 同时为函数类型指定了参数类型和返回值类型
// (x, y) => { return x + y } 这是函数的实现
let Add: (x: number, y: number) => number = (x, y) => {
  return x + y
}
console.log('Add', Add(3, 4)) // Add 11

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
2.2 可选参数

在TypeScript里我们可以在参数名后面使用 ?实现可选参数的功能,可选参数就是 参数是可选的,可传可不传。

function helloName(name1: string, name2?: string) {
  console.log(`hello:${name1},${name2}`)
}

helloName('筱竹', 'TS') // hello:筱竹,TS
helloName('筱竹') // hello:筱竹,undefined

function helloAge(age1?: number, age2?: number) {
  console.log(`age:${age1},${age2}`)
}

helloAge(18, 3) // age:18,3
helloAge(18) // age:18,undefined
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

注意:必选参数不能位于可选参数后,如下所示:

// 注意必选参数不能位于可选参数后,如下所示(错误的):

function helloNum(num1?: number, num2: number) {
  console.log(`hello:${num1},${num2}`)
}

helloNum(1, 2)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上面的代码把鼠标移动到num2参数上是会提示:必选参数不能位于可选参数后,如下图所示:

在这里插入图片描述

2.3默认参数

在TypeScript中,我们也可以为参数提供一个默认值,假如用户没有传递这个参数或传递的值是undefined时,就会使用默认参数的值。

// 在TypeScript里,我们也可以为参数提供一个默认值,假如用户没有传递这个参数或传递的值是undefined时,就会使用默认参数的值。

function sayHi(name?: string, age = 18) {
  console.log(`hi大家好,我是${name},我今年:${age}`)
}

sayHi('筱竹') // hi大家好,我是筱竹,我今年:18了
sayHi('筱竹', 20) // hi大家好,我是筱竹,我今年:20了
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
2.4对象类型
const obj1: {name: string; age: number; sayHi(): void} = {
  name:'张三',
  age:10,
  sayHllo(){
    console.log('hello')
  }
}
console.log(obj1.age) // 10
obj1.sayHllo() // hello
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
// 把属性分成多行显示时,分号可以省略
const obj2: {
  name: string
  age: number
  sayHllo(): void
} = {
  name:'张三',
  age:10,
  sayHllo(){
    console.log('hello')
  }
}
console.log(obj2.age) // 10
obj2.sayHllo() // hello
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
2.5类型别名(Type Alias)

使用类型别名 可以为任意类型指定别名

使用场景:当同一个类型被重复使用多次时,可以使用类型别名来简化该类型的使用。

 type typeObj = {
  name: string
  age: number
  sayHllo:() => void
}

const obj3: typeObj = {
  name: '张三2',
  age: 31,
  sayHllo:() =>{
    console.log('hello') // hello
  }
}
obj3.sayHllo() 

// ------------

type typeArr = (number | string)[]
let arr1: typeArr = [1, 2, '3', 4]
let arr2: typeArr = ['张三', 18, '李四', 14]
console.log('arr1', arr1) // arr1 [ 1, 2, '3', 4 ]
console.log('arr2', arr2) // arr2 [ '张三', 18, '李四', 14 ]

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
2.6接口(interface)

因为TypeScript的核心原则之一是对值所具有的结构进行类型检查,那么接口(interface)的作用就为对象中属性的类型命名定义契约(描述对象属性值的类型),同时当一个对象类型被多次重复使用时,使用接口(interface),也能够达到复用的目的。

interface typeObj {
  name: string
  age: number
  sayHllo: () => void
}

const person1: typeObj = {
  name: '张三',
  age: 19,
  sayHllo: () => {
    console.log('hello') // hello
  },
}

const person2 = <typeObj>{
  name: '李四',
  age: 18,
  sayHllo: () => {
    console.log('hi') // hi
  },
}

person1.sayHllo() 

person2.sayHllo()

let person3 = {
  name: '王五',
  age: 20,
  sayHllo: () => {
    console.log('您好')
  },
}
let foo = (lableObj: typeObj) => {
  console.log(lableObj.age) // 20
  lableObj.sayHllo() // 您好
}

foo(person3)

  • 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

注意:

  • 传入的对象必须满足接口中的必要的属性,那么它就是被允许的
interface typeObj {
  name: string
  age: number
  sayHllo: () => void
}

let person4 = {
  name: '赵六',
  sayHllo: () => {
    console.log('您好')
  },
}

let foo = (lableObj: typeObj) => {
  lableObj.sayHllo() 
}

foo(person4) // 没有传入必要的age属性会报错,Error: Property 'age' is missing in type '{ name: string; sayHllo: () => void; }' but required in type 'typeObj'.
 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以
interface typeObj {
  name: string
  age: number
  sayHllo: () => void
}

let person5 = {
  age: 21,
  sayHllo: () => {
    console.log('您好')
  },
  name: '赵六',
}
let foo = (lableObj: typeObj) => {
  lableObj.sayHllo() // 您好
}

foo(person5)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

类型别名(type)与接口(interface)区别:

**相同点:类型别名(type)接口(interface)**都可以描述一个对象(给对象指定类型),如

 
 type typeObj = {
  name: string
  age: number
  sayHllo:() => void
}

interface typeObj {
  name: string
  age: number
  sayHllo: () => void
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

不同点:接口(interface) 只能为对象指定类型,而 **类型别名(type)**不仅仅能够为对象指定类型,而且能够为任意类型指定别名。

type Name = string // 基本类型
let Name = '筱竹'
console.log('Name', Name) // Name 筱竹

type NameAge = string | number // 联合类型
let NameAge = 18
console.log('NameAge', NameAge) // NameAge 18

type typeArr = [string, number, boolean] // 元组类型
let Arr: typeArr = ['筱竹', 18, true]
console.log('Arr', Arr) // Arr [ '筱竹', 18, true ]


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
2.7 可选属性

接口里的属性不全都是必需的,有些是只在某些条件下存在,或者根本不存在。给函数传入的参数对象中只有部分属性赋值了。带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?符号。

// 对象中的【可选属性】(method属性可以省略),【可选属性】名字定义的后面加一个?符号,
// 可选属性的2个好处:
// 1.是可以对可能存在的属性进行预定义
function myAxios(config: {url: string; method?: string}){
  console.log(config)
}
myAxios({url:'https://api.twitter.com'}) 

// 2.是可以捕获引用了不存在的属性时的错误
// 我们故意将 createSquare里的color属性名拼错,就会得到一个错误提示:
interface GetConfig {
  url: string;
  method?: string;
}

function myAxios(config: GetConfig): { url: string; method?: string } {
let newMethod = {url: "https://www.baidu.com", method: 'Get'};
  if (config.url) {
    newMethod.url = config.url;
  }
  if (config.methods) {
     // Error: Property 'methods' does not exist on type 'GetConfig'. Did you mean 'method'?
    newMethod.method = config.method;
  }
  return newMethod;
}

let mySquare = myAxios({url: "https://www.boxuegu.com"});

  • 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
2.8 继承接口

能够从一个接口里复制属性或者方法到另一个接口里,可以更灵活地将接口抽离到可重用的模块里,实现复用。

// interface infObj1 { x: number; y: number }
// interface infObj2 { x: number; y: number; z: number }

// 以上简写方式(使用接口继承)
interface infObj1 {
  x: number
  y: number
}
// 接口infObj2 继承了 infObj1中的属性
interface infObj2 extends infObj1 {
  z: number
}

const positionObj: infObj2 = {
  x: 1,
  y: 1,
  z: 3,
}
console.log('positionObj.x', positionObj.x) // positionObj.x 1
console.log('positionObj.z', positionObj.z) // positionObj.z 3

// 接口infObj3 继承了 infObj1中的属性
interface infObj3 extends infObj1 {
  z: number
}
// 使用接口 infObj3
const positionObj1 = <infObj3>{
  x: 1,
  y: 1,
  z: 3,
}
console.log('positionObj1.x', positionObj1.x) // positionObj1.x 1
console.log('positionObj1.z', positionObj1.z) // positionObj1.z 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

4、TypeScript常用类型(二)

4.1 类型推论

在TS中,有些没有明确指出类型的地方,TS中的类型推论会帮助提供类型,也就是说即使有些地方不写类型注解,TS也会根据类型推论的机制来提示类型。

类型推论可以分为基础的类型推论和上下文推论

基础的类型推论如下:

鼠标放到变量名称上去后的,TS类型推论机制会帮助提示类型,如下图所示:

在这里插入图片描述

代码如下所示:

let userName = 'xiaozhu' // 类型推论 为 string 类型
// 等价于 let userName:string = 'xiaozhu' 类型注解:string可以省略

let userAge = 3 // 类型推论 为 number 类型

// 等价于 let userAge:number = 3 类型注解:number可以省略

let userInfo = ['筱竹', 18, true, undefined] // 类型推论为(string | number boolean | undefined)[]

/**
 * 等价于 let userInfo:(string | number boolean | undefined)[] = ['筱竹', 18, true, undefined]
 * 类型注解 :(string | number boolean | undefined)[] 可以省略
 *
 **/


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

上下文推论

顾名思义,所谓上下文推论,就是TS的类型推论根据变量所在的上下文环境进行推论变量的类型。如下代码所示:

// 推论出函数参数 num1 的类型是number类型,返回值的类型也是 number类型
function fn(num1 = 10, num2: number) {
  return num1 + num2
}
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述

需要注意的是,虽然TS中有类型推论的机制来帮助我们提供类型,提升开发效率,但并不代表我们在任何场景下都不需要写类型注解,而完全依赖于类型推论的帮助提供类型,而是要分清楚情况,TS的类型机制适用于如下3个场景:

1、变量初始化赋值时

let userName = 'xiaozhu' // 类型推论 为 string 类型
  • 1

2、函数中有默认值的函数参数

// 推论出函数参数 num1 的类型是number类型,返回值的类型也是 number类型
function fn(num1 = 10, num2: number) {
  return num1 + num2
}

  • 1
  • 2
  • 3
  • 4
  • 5

3、函数中函数返回的类型

// 推论出函数返回值的类型是 number类型
function fn(num1:number, num2: number) {
  return num1 + num2
}
  • 1
  • 2
  • 3
  • 4
4.2 类型断言

TypeScript的类型检查不一定是最准确的,有时候,我们需要手动的指定值的类型,才能得到我们想要的类型,如下图代码所示:

以下是通过类型检查,显示 divEl 的类型可能是 HTMLElement 或者是 null,但是我们根据代码更直观的看出,divEl 的类型就是 HTMLElement,所以这个时候,我们比TS 更清楚一个值的类型,这时候我们就可以使用类型断言来指定更具体的类型了。

根据以上代码,使用类型断言如下所示:

const divEl = document.getElementById('div') as HTMLElement
  • 1

类型断言使用 as 语法来实现,as 后面的类型是具体的类型。

以上代码,使用类型断言后,通过类型推断得出,divEl 的类型可能是 HTMLElement,没有了 null类型。如下图所示:

在这里插入图片描述

类型断言除了使用 as 语法以外,也可以使用 <> 语法,如下代码所示:

const divEl = <HTMLElement>document.getElementById('div')
  • 1

由于TS中的<> 语法表示类型断言,在结合JSX中的<> 语法,代码解析上有冲突,所以不建议使用<> 这个类型断言语法,更建议使用 as语法,同时TS在 .tsx文件里禁用了使用<> 语法的类型断言。

4.3 字面量类型

在TS中的字面量类型,跟我们在JS中的字面量类型理解上还不太一样,如下代码所示:

// 以下代码中的字面量类型是什么?
let num1 = 12 // number 类型
const num2 = 13 // 13 类型,why?
  • 1
  • 2
  • 3

我们根据TS中的类型推论,不难看出 num2 的类型是 确实是 13类型,不是我们直观意义上的 number类型,为什么13可以作为类型呢?在TS中,这就叫字面量类型(Literal Types)。

在这里插入图片描述

由于 let 声明的变量,变量的值是可以变化的(可以重新赋值),所以类型为 number 类型。

由于 const声明的变量,变量的值不能变化(被赋值后,不能再改变),只能是 13类型,所以类型为 13 类型。

在TS中字面量类型主要包括:字符串字面量类型数字字面量类型布尔字面量类型对象字面量类型,它们都可以直接作为类型注解进行使用,如下代码所示:

// 字符串字面量类型
const str: 'xiaozhu' = 'xiaozhu'
// 数字字面量类型
const num: 18 = 18
// 布尔字面量类型
const bool: true = true
// 对象字面量类型

interface ObjInfo {
  name: 'xiaozhu'
  age: 16
}
const obj: ObjInfo = {
  name: 'xiaozhu',
  age: 16,
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在TS中字面量类型相较于TS基础类型(原始类型)更加精确、严谨,因为字面量类型要求变量的值,必须跟字面量完全一致才行,不然就会报错,而TS基础类型,只要是符合基础类型中的任意同类型值就可以,就不会报错。

如下图所示:

在这里插入图片描述

在实际应用中,TS中的字面量类型常与联合类型、接口(或类型别名)等一起搭配使用,用来表示一组明确的可选值列表,如下代码所示:

interface Options {
  behavior: 'go' | 'jump' | 'run' | 'sit'
  sex: '男' | '女'
  isShow: true | false
  age: 20 | 30 | 40 | 50
  say: (() => void) | null 
}

const people: Options = {
  behavior: 'go',
  sex: '女',
  isShow: true,
  age: 20,
  say: null,
}

// ---------------

type Direction = 'up' | 'right' | 'down' | 'left'

function move(config: Direction) {
  console.log(config) // up
}
move('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
4.4 枚举

枚举(Enum)是TS中新增的一个类型,枚举用来定义一个带名字的常量集合,枚举类型的使用类似于字面量类型+联合类型的组合使用,enum是枚举的语法。

我们对以下的类型别名代码,改造成枚举类型。

type Direction = 'up' | 'right' | 'down' | 'left'

// 改造成枚举类型,
// 可以看出枚举定义了一个带名字的常量的集合,这样的好处相对于以上类型别名的代码写法更加简洁

enum Direction2 {
  up,
  right,
  down,
  left
}

function go(config: Direction2) {
  console.log(config) // 0
}
// 在枚举中,通过点语法来获取枚举中的常量成员
go(Direction2.up)


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

当我们把鼠标移动到枚举常量成员上时,发现枚举常量成员是有值的,枚举第一个成员默认值是 0,剩下成员的值依次累加1,这也就解释了,为什么 在go函数中,console.log(config) 的值为什是0了。

在这里插入图片描述

TS中的枚举主要包括:数字枚举、字符串枚举、异构枚举、常量成员和计算成员、枚举成员类型和联合枚举、常量枚举、外部枚举

4.4.1 数字枚举

以下代码,我们定义了一个数字枚举 up的值默认从 0开始,其它枚举中的成员会从1开始递增1。

// 数字枚举

enum Direction2 {
  up,
  right,
  down,
  left
}

jump(Direction4.left)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

以下代码,我们定义了一个数字枚举, up使用初始化为 10。 其余的成员会从 10开始自动递增1。

// 数字枚举

enum Direction3 {
  up = 10,
  right,
  down,
  left,
}

function run(option: Direction3) {
  console.log(option) // 11
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

以下代码,我们定义了一个数字枚举,给枚举中的成员分别初始化一个数字。

// 数字枚举
enum Direction4 {
  up = 10,
  right = 14,
  down = 18,
  left = 20,
}

function jump(info: Direction4) {
  console.log(info) // 20
}

jump(Direction4.left)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

4.4.2 字符串枚举

在一个字符串枚举里,枚举中每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。

因为字符串枚举成员不能自增,所以需要给每个字符串枚举成员设置初始值。

// 字符串枚举(用字符串字面量初始化枚举中的成员)

enum Direction5 {
  up = 'UP',
  down = 'DOWN',
  left = 'LEFT',
  right = 'RIGHT',
}

// 字符串枚举(用另外一个字符串枚举成员进行初始化)
enum Direction6 {
  up = Direction5.up,
  down = Direction5.down,
  left = Direction5.left,
  right = Direction5.right,
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

4.4.3 异构枚举

一个枚举中同时包含字符串成员和数字成员,这样的枚举就称之为异构枚举。

enum Direction7 {
  name = 'xiaozhu',
  age = 18,
}
  • 1
  • 2
  • 3
  • 4

4.4.4 常量成员计算成员

以下代码中 Info1Info2中枚举成员都是常量成员

enum Info1 {
  num1,
  num2,
  num3,
}

enum Info2 {
  num1 = 1,
  num2,
  num3,
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

当一个表达式满足下面条件之一时,它就是一个常量枚举表达式

  • 一个枚举表达式字面量(主要是字符串字面量或数字字面量)
  • 一个对之前定义的常量枚举成员的引用(可以是在不同的枚举类型中定义的)
  • 带括号的常量枚举表达式
  • 一元运算符 +, -, ~其中之一应用在了常量枚举表达式
  • 常量枚举表达式做为二元运算符 +, -, *, /, %, <<, >>, >>>, &, |, ^的操作对象。 若常数枚举表达式求值后为 NaNInfinity,则会在编译阶段报错。

除了以上情况之外,所有其它情况的枚举成员都是计算成员

常量成员和计算成员代码如下所示:

enum FileAccess {
  // 常量成员
  None,
  Read = 1 << 1,
  Write = 1 << 2,
  ReadWrite = Read | Write,
  // 计算成员
  G = '123'.length,
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

4.4.5 枚举成员类型和联合枚举

4.4.6 常量枚举

常量枚举通过在枚举上使用 const修饰符来定义。

const enum Direction8 {
  up = 'UP',
  down = 'DOWN',
  left = 'LEFT',
  right = 'RIGHT',
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

常量枚举只能使用常量枚举表达式(常量枚举不允许包含计算成员),并且不同于常规的枚举,它们在编译阶段会被删除。常量枚举成员在使用的地方会被内联进来。 因此常量枚举的成员都必须是常量成员,如下代码所示:

// code.ts
const enum Direction9 {
  up,
  down,
  left,
  right,
}

let info = [Direction9.up, Direction9.down, Direction9.left, Direction9.right]

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

通过 tsc code.ts 命令,生成 code.js 文件。TS代码编译成 JS代码后,Direction9 枚举的定义被删除了,info 数组中对 Direction9 的引用也变成了常量值的引用(Direction9.up 内联了 0、Direction9.down 内联了 1、Direction9.left 内联了 2 、Direction9.right 内联了 3),如下代码所示:

// code.js
var info = [0 /* Direction9.up */, 1 /* Direction9.down */, 2 /* Direction9.left */, 3 /* Direction9.right */]

  • 1
  • 2
  • 3

4.4.7 外部枚举

在 TS 中,可以通过 declare 关键字描述一个已经存在的枚举类型,通过 declare 定义出来的 枚举类型 叫 外部枚举。

declare enum UserInfo {
  name = 'xiaozhu',
  age = 18,
}

  • 1
  • 2
  • 3
  • 4
  • 5

在TS中,其它类型仅仅被当做类型,而枚举不仅用作类型使用,还可以提供值(枚举成员都是有值的),也就说,在TS中其它的类型被当做类型时,TS代码会在编译为JS代码时,自动把类型删除掉,而枚举类型会被编译为JS代码。

// code.ts

// 字符串类型
let userName: string = 'xiaozhu' 

// 枚举类型
enum Description10 {
  run = 'RUN',
  jump = 'JUMP',
  go = 'go',
  sit = 'SIT',
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

以上代码,通过 tsc code.ts命令,会编译成如下代码:

// code.js

// 字符串类型
var userName = 'xiaozhu'; // 自动把:string 类型注解删除掉了

// 枚举类型
// 枚举类型会编译为JS代码,不会把枚举类型和类型的值删除掉
var Description10;
(function (Description10) {
    Description10["run"] = "RUN";
    Description10["jump"] = "JUMP";
    Description10["go"] = "go";
    Description10["sit"] = "SIT";
})(Description10 || (Description10 = {}));

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

后续TypeScript学习总结(二)高级类型等内容正在编写中,敬请期待。☀️

在这里插入图片描述

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号