持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天,点击查看活动详情
TypeScript 概述
概念
- TypeScript 是微软开发的编程语言,它是 JavaScript 的超集,可以在任何运行 JavaScript 的地方运行,官方文档,中文文档,不再维护。
- TypeScript =
Type+ JavaScript(在 JS 基础之上,为 JS 添加了类型支持/类型检测)。
let age1: number = 18 // TS 代码 => 有明确的类型,即 number(数值类型)
优势
-
背景:JS 的类型系统存在“先天缺陷”,是弱类型语言,而代码中的大部分错误都是类型错误(TypeError),这些经常出现的错误,导致了在使用 JS 进行项目开发时,增加了找 Bug、改 Bug 的时间,严重影响开发效率。
-
发现错误的时机更早。
- 对于 JS 来说:需要等到代码真正去执行的时候才能发现错误(晚);
- 对于 TS 来说:在代码编译的时候(代码执行前)就可以发现错误(早),配合 VSCode 等开发工具,发现错误的时机可以提前到在编写代码的时候,减少找 Bug、改 Bug 时间。
-
代码提示,随时随地的安全感,增强了开发体验。
-
支持最新的 ECMAScript 语法,优先体验最新的语法,让你走在前端技术的最前沿。
-
Vue3 源码使用 TS 重写、Angular 默认支持 TS、React 与 TS 完美配合,TypeScript 已成为大中型前端项目的首选编程语言,前端最新的开发技术栈离不开 TS,例如 React(TS + Hooks),Vue(TS + Vue3)。
// 使用 JavaScript:在 VSCode 里面写代码;在浏览器中运行代码,发现错误【晚】。
// 使用 TypeScript:在 VSCode 里面写代码;写代码的同时,就会发现错误【早】;在浏览器中运行代码。
let num = 123
num = 'abc'
num.toFixed(2) // Uncaught TypeError: num.toFixed is not a function
编译 TypeScript
安装编译 TS 的工具
- 问题:为什么要安装编译 TS 的工具包?
- 回答:Node.js/浏览器,只认识 JS 代码,不认识 TS 代码,因此需要先将 TS 代码转化为 JS 代码,然后才能运行。
- 安装命令:
npm i -g typescript或者yarn global add typescript。 - 验证是否安装成功:tsc –v(查看 TypeScript 的版本)。
编译并运行 TS 代码
- 创建
hello.ts文件(注意:TS 文件的后缀名为.ts)。 - 将 TS 编译为 JS:在终端中输入命令,
tsc hello.ts(此时,在同级目录中会出现一个同名的 JS 文件)。 - 执行 JS 代码:在终端中输入命令,
node hello.js。 - 说明:所有合法的 JS 代码都是 TS 代码,有 JS 基础只需要学习 TS 的类型即可。
- 注意:由 TS 编译生成的 JS 文件,代码中就没有类型信息了。
# 监听 index.ts 文件的变化并编译
tsc -w index.ts # 窗口 1
# 运行编译后的代码
nodemon index.js # 窗口 2
TypeScript 基础
类型注解
什么是 TypeScript 的类型注解?
内容
- TypeScript 是 JS 的超集,TS 提供了 JS 的所有功能,并且额外的增加了:类型系统,JS 虽然也有类型(比如,number/string 等),但 JS 并不会对类型进行校验和提示。
- TypeScript 类型系统的主要优势:校验和提示。
let age: number = 18
- 说明:代码中的
:number就是类型注解。 - 作用:为变量添加类型约束,比如上述代码中,约定变量 age 的类型为 number 类型。
- 解释:约定了什么类型,就只能给变量赋值该类型的值,也会出现该类型相关的提示。
// 错误原因:将 string 类型的值赋值给了 number 类型的变量,类型不一致
let age: number = '18'
原始类型
TS 中原始类型的怎么使用?
内容
可以将 TS 中的常用基础类型细分为两类,分别是 JS 已有类型和 TS 新增类型。
- JS 已有类型。
// 原始类型:`number/string/boolean/null/undefined/symbol/bigint`
const age: number = 18
const myName: string = 'Ifer'
const isLoading: boolean = false
// ...
-
TS 新增类型。
a,联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any 等。
b,注意:TS 中的原始类型和 JS 中写法一致;TS 中的对象类型在 JS 类型基础上更加细化,每个具体的对象(比如数组、对象、函数)都有自己的类型语法。
数组类型
目标
数组类型的两种写法。
内容
// 写法 1
let numbers: number[] = [1, 3, 5]
// 写法 2
let strings: Array<string> = ['a', 'b', 'c']
strings.push('d') // 后续 push 的数据也必须是字符串
联合类型
通过联合类型将多个类型组合成一个类型。
内容
- 需求:数组中既有 number 类型,又有 string 类型,这个数组的类型应该如何写?
// 定义一个数组,数组中可以有数字或者字符串, 需要注意 | 的优先级
let arr: (number | string)[] = [1, 'abc', 2]
- 解释:
|(竖线)在 TS 中叫做联合类型,即由两个或多个其他类型组成的类型,表示可以是这些类型中的一种。 - 注意:这是 TS 中联合类型的语法,只有一根竖线,不要与 JS 中的或(||)混淆了。
- 场景:定时器的初始变量定义。
// 有问题的代码
let timer = null
// Type 'number' is not assignable to type 'null'.
timer = setInterval(() => {})
// 解决,思考除了下面方法还有其他办法吗?
let timer: number | null = null
timer = setInterval(() => {})
// 忽略
// 通过 tsc --init 命令可以生成配置文件
// 通过 strictNullChecks 指定为 true 可以开启对 null 和 undefined 的检测
// 即便开启了检测,当 null 赋值给某个变量时,这个变量会被推断为 any 类型
// !通过 noImplicitAny 指定为 false 可以禁用 any 类型,此时 null 赋值给某个变量时将会是 null 类型
let timer: number | null = null
timer = setInterval(() => {}, 1000)
类型别名
使用类型别名给类型起别名。
内容
- 类型别名作用:为任意类型起别名,别名甚至可以是中文。
type s = string
const myName: s = 'ifer'
type 字符串类型 = string
const myAddress: 字符串类型 = '点点VS叉叉'
- 使用场景:当同一类型(复杂)且可能被多次使用时,可以通过类型别名,简化该类型的使用。
type CustomArray = (number | string)[]
let arr1: CustomArray = [1, 'a', 3, 'b']
let arr2: CustomArray = ['x', 'y', 6, 7]
-
解释说明。
a,使用
type关键字来创建自定义类型。b,类型别名(比如,此处的 CustomArray)可以是任意合法的变量名称。
c,推荐使用大写字母开头。
d,创建类型别名后,直接使用该类型别名作为变量的类型注解即可。
函数类型
基本使用
如何给函数指定类型?
内容
函数的类型实际上指的是:函数参数 和 返回值 的类型,为函数指定类型有如下两种方式。
- 单独指定参数、返回值的类型。
// 函数声明
function add(num1: number, num2: number): number {
return num1 + num2
}
// 箭头函数
const add = (num1: number, num2: number): number => {
return num1 + num2
}
- 同时指定参数、返回值的类型。
// 解释:可以通过类似箭头函数形式的语法来为函数添加类型,注意这种形式只适用于函数表达式。
type AddFn = (num1: number, num2: number) => number
const add: AddFn = (num1, num2) => {
return num1 + num2
}
void 类型
目标
能够了解 void 类型的使用。
内容
- 基础使用。
// 注意:在没有开始 strictNullChecks 模式的情况下,可以把 null 和 undefined 赋值给任意类型
// 如何开启:通过 tsc --init 生成配置文件,默认就会开启 strictNullChecks
// let temp: void = null // ok
let temp: void = undefined // ok
- 如果函数没有返回值,那么函数返回值类型为:
void。
function greet(name: string): void {
console.log('Hello', name)
// return undefined // 默认有这么一句
}
- 注意:如果一个函数明确了返回类型是 undefined,则必须显示的
return undefined。
const add = (): undefined => {
return undefined
}
可选参数
使用 ? 给函数指定可选参数类型
内容
- 使用函数实现某个功能时,参数可以传也可以不传,这种情况下,在给函数参数指定类型时,就用到可选参数了。
- 比如,数组的 slice 方法,可以
slice()也可以slice(1)还可以slice(1, 3)。 - 可选参数语法:在可传可不传的参数名称后面添加
?(问号)。
// start、end 可传可不传,传就传 number 类型
function mySlice(start?: number, end?: number): void {
console.log('起始索引:', start, '结束索引:', end)
}
- 注意:可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数。
参数默认值
给函数指定默认值。
内容
通过赋值符号(=)可以给参数执行默认值,注意:参数默认值和可选参数互斥的,只能指定其中一种。
// Error: Parameter cannot have question mark and initializer
function mySlice(start: number = 0, end?: number = 0) {}
// 可选参数
function mySlice(start: number = 0, end?: number) {}
// 默认值
function mySlice(start: number = 0, end: number = 0) {}
对象类型
基本使用
对象类型的基本使用。
内容
JS 中的对象是由属性和方法构成的,而 TS 对象的类型就是在描述数据的结构(有什么样类型的属性和方法)。
- 基本使用。
const person: object = {}
- 另一种使用方式。
// 左边的 {} 表示类型(严格来说应该是对象字面量类型),右边的 {} 表示值
let person: {} = {}
- 可以精确描述对象里面具体内容的类型。
// 要求必须指定 string 类型的 name 属性,左右两边数量保持一致
const person: { name: string } = {
name: '点点',
}
const obj = {
name: '点点',
age: 18,
}
// 右边是变量,在满足左边声明的前提下(右边内容可以比左边多)
const person: { name: string } = obj
// 字符串比较特殊,满足左边的类型要求即可
const str: { length: number } = 'hello'
- 描述对象中方法的类型。
// 在一行代码中指定对象的多个属性类型时,使用 `;`(分号)来分隔
// 单独制定函数的参数和返回值
// const person: { name: string; add(n1: number, n2: number): number } = {
// 可以统一指定函数的参数和返回值
const person: { name: string; add: (n1: number, n2: number) => number } = {
name: '点点',
add(n1, n2) {
return n1 + n2
},
}
- 也可以通过换行来分隔多个属性类型,去掉
;。
const person: {
name: string
add(n1: number, n2: number): number
} = {
name: '点点',
add(n1, n2) {
return n1 + n2
},
}
- 定义对象类型时也可以结合类型别名来使用。
type Person = {
name: string
add(n1: number, n2: number): number
}
const person: Person = {
name: '点点',
add(n1, n2) {
return n1 + n2
},
}
小结
- 使用
{}来描述对象/数据结构。 - 属性采用
属性名: 类型的形式。 - 方法采用
方法名(): 返回值类型的形式。
对象可选属性
- 对象的属性或方法,也可以是可选的,此时就用到可选属性了。
- 比如,我们在使用
axios({ ... })时,如果发送 GET 请求,method 属性就可以省略。 - 可选属性的语法与函数可选参数的语法一致,都使用
?来表示。
type Config = {
url: string
method?: string
}
function myAxios(config: Config) {
console.log(config)
}
实例
创建两个学生对象:包含姓名、性别、成绩、身高、学习、打游戏。
type Student = {
name: string
gender: string
score: number
height: number
study(): void
play: (name: string) => void
}
const stu: Student = {
name: 'xxx',
gender: 'man',
score: 88,
height: 178,
study() {
console.log('点点点')
},
// play() 这里不写参数,也不会马上报错,但 stu.play() 调用的时候就知道了
play(name) {},
}
接口
当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的。
- 使用
interface关键字来声明接口。 - 接口名称(比如,此处的 IPerson),可以是任意合法的变量名称,推荐以
I开头。 - 声明接口后,直接使用接口名称作为变量的类型。
- 因为每一行只有一个属性类型,因此,属性类型后没有
;(分号)。
interface IStudent {
name: string
gender: string
study(): void
}
const stu: IStudent = {
name: 'xxx',
gender: 'man',
study() {
console.log('点点点')
},
}
接口继承
如果两个类型之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用。
- type 方式。
type Point2D = {
x: number
y: number
}
type Point3D = {
x: number
y: number
z: number
}
- interface 方式。
interface Point2D {
x: number
y: number
}
// 使用 `extends`(继承)关键字实现了接口 Point3D 继承 Point2D
// 继承后,Point3D 就有了 Point2D 的所有属性和方法(此时,Point3D 同时有 x、y、z 三个属性)
interface Point3D extends Point2D {
z: number
}
interface vs type
相同点
- 都可以描述对象或者函数。
// interface 描述对象
interface IPerson {
name: string
age: number
}
const p: IPerson = { name: 'ifer', age: 18 }
// interface 描述函数
interface ISetPerson {
(name: string, age: number): void
}
const setPerson: ISetPerson = (name, age) => {}
setPerson('ifer', 18)
// type 描述对象
type TPerson = {
name: string
age: number
}
const p: TPerson = { name: 'ifer', age: 18 }
// type 描述函数
type TSetPerson = {
(name: string, age: number): void
}
const setPerson: TSetPerson = (name, age) => {}
setPerson('ifer', 18)
- 都允许拓展,语法不一样。
// interface extends interface
interface IName {
name: string
}
interface IPerson extends IName {
age: number
}
const p: IPerson = {
name: 'ifer',
age: 18,
}
// interface extends type
type TName = { name: string }
interface IPerson extends TName {
age: number
}
const p: IPerson = {
name: 'ifer',
age: 18,
}
// type & type
type TName = { name: string }
type TPerson = { age: number } & TName
const p: TPerson = {
name: 'ifer',
age: 18,
}
// type & interface
interface IName {
name: string
}
type TPerson = { age: number } & IName
const p: TPerson = {
name: 'ifer',
age: 18,
}
不同点
type 除了可以描述对象或函数,实际上可以为任意类型指定别名。
type NumStr = number | string
相同的 interface 声明能够合并,相同的 type 声明会报错。
interface IPerson {
name: string
}
interface IPerson {
age: number
}
const p: IPerson = {
name: 'ifer',
age: 18,
}
总结:一般使用 interface 来描述对象结构,用 type 来描述类型关系。
元组类型
- 使用
number[]的缺点:不严谨,因为该类型的数组中可以出现任意多个数字。 元组 Tuple,元组是特殊的数组类型,它能确定元素的个数以及特定索引对应的类型。
const position: [number, number] = [39.5427, 116.2317]
-
解释说明。
a,元组类型可以确切地标记出有多少个元素,以及每个元素的类型。
b,该示例中,元素有两个元素,每个元素的类型都是 number。
// 可以给元组中的元素起别名
const arrTuple: [height: number, age: number, salary: number] = [170, 20, 17500]
类型推论
- 在 TS 中,某些没有明确指出类型的地方,TS 的类型推论机制会帮助提供类型。
- 换句话说:由于类型推论的存在,这些地方,类型注解可以省略不写。
- 常见的发生类型推论的 2 种场景:声明变量并初始化时;决定函数返回值时。
// 变量 age 的类型被自动推断为:number
let age = 18
const obj = {
name: 'ifer',
age: 18,
show() {},
}
// 函数返回值的类型被自动推断为:number
function add(num1: number, num2: number) {
return num1 + num2
}
- 推荐:代码写熟了之后,有类型推论的情况下可以省略类型注解,充分利用 TS 类型推论的能力,提升开发效率。
- 技巧:如果不知道类型,可以通过鼠标放在变量名称上,利用 VSCode 的提示来查看类型。
- 建议:在 VSCode 中写代码的时候,多看方法、属性的类型,养成写代码看类型的习惯,例如
const oDiv = document.createElement('div')。
字面量类型
基本使用
思考以下代码,两个变量的类型分别是什么?
let str1 = 'Hello TS'
const str2 = 'Hello TS'
通过 TS 类型推论机制,可以得到答案:变量 str1 的类型为:string,变量 str2 的类型为:'Hello TS'。
- str1 是一个变量,它的值可以是任意字符串,所以类型为:string。
- str2 是一个常量,它的值不能变化只能是 'Hello TS',所以,它的类型为:'Hello TS'(字符串字面量类型)。
- 注意:此处的 'Hello TS',就是一个字符串字面量类型,也就是说某个特定的字符串也可以作为 TS 中的类型。
- 任意的 JS 字面量都可以作为类型使用,例如
{ name: 'jack' }、[]、18、'abc'、false、function() {}等。
使用方式和场景
- 使用方式:字面量类型常配合联合类型一起使用。
- 使用场景:用来表示一组明确的可选值列表,比如在贪吃蛇游戏中,游戏方向的值只能是上、下、左、右中的一个。
type Direction = 'up' | 'down' | 'left' | 'right'
function changeDirection(direction: Direction) {
console.log(direction)
}
changeDirection('up') // 调用函数时,会有类型提示
- 解释:参数 direction 的值只能是 up/down/left/right 中的任意一个。
- 优势:相比于 string 类型,使用字面量类型更加精确、严谨。
- 其他应用场景,性别和 Redux 中的 Action 等等。
type Gender = '男' | '女'
const zs: Gender = '男'
type Action = {
type: 'TODO_ADD' | 'TODO_DEL' | 'TODO_CHANGE' | 'TODO_FIND'
}
function reducer(state, action: Action) {
switch (action.type) {
case 'TODO_ADD': // 这里会自动具有提示
}
}
枚举类型
基本使用
- 枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值。
- 枚举:定义一组命名常量,它描述一个值,该值可以是这些命名常量中的一个。
- 使用
enum关键字定义枚举,约定枚举名称以大写字母开头。 - 枚举中的多个值之间通过
,(逗号)分隔,定义好枚举后,直接使用枚举名称作为类型注解。
// 创建枚举
enum Direction {
Up,
Down,
Left,
Right,
}
// 可以当做类型使用枚举
function changeDirection(direction: Direction) {
console.log(direction)
}
// 也可以当做值使用枚举
// 调用函数时,需要传入:枚举 Direction 成员的任意一个,类似于 JS 中的对象,直接通过点(.)语法 访问枚举的成员
changeDirection(Direction.Up)
数字枚举
- 问题:我们把枚举成员作为了函数的实参,它的值是什么呢?
- 解释:通过将鼠标移入 Direction.Up,可以看到枚举成员 Up 的值为 0。
- 注意:枚举成员是有值的,默认为:从 0 开始自增的数值。
- 我们把枚举成员的值为数字的枚举称为:
数字枚举。 - 当然,也可以通过“等号”给枚举中的成员指定初始值,如下所示。
// Down -> 11、Left -> 12、Right -> 13
enum Direction {
Up = 10,
Down,
Left,
Right,
}
enum Direction {
Up = 2,
Down = 4,
Left = 8,
Right = 16,
}
console.log(Direction['Up']) // 2
// 也可以反向操作
console.log(Direction[2]) // Up
实现原理
- 枚举类型比较特殊,不仅仅可以用作类型,还可以当做值使用,因为枚举成员都是有值的。
- 也就是说,其他的类型会在编译为 JS 代码时自动移除,但是,枚举类型会被编译为 JS 代码。
- 说明:枚举与前面讲到的字面量类型 + 联合类型组合的功能类似,都用来表示一组明确的可选值列表。
- 推荐:字面量类型 + 联合类型组合的方式,因为相比枚举,这种方式更加直观、简洁、高效。
enum Direction {
Up = 2,
Down = 4,
Left = 8,
Right = 16,
}
// 会被编译为以下 JS 代码:
var Direction
;(function (Direction) {
Direction[(Direction['Up'] = 2)] = 'Up'
Direction[(Direction['Down'] = 4)] = 'Down'
Direction[(Direction['Left'] = 8)] = 'Left'
Direction[(Direction['Right'] = 16)] = 'Right'
console.log(Direction)
})(Direction || (Direction = {}))
字符串枚举
- 定义:枚举成员的值是字符串称为字符串枚举。
- 注意:字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值。
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
🧐 具体的使用案例。
enum Gender {
女,
男,
}
type User = {
name: string
age: number
// gender: '男' | '女' // 但后台需要 0 和 1
gender: Gender
}
const user: User = {
name: 'ifer',
age: 18,
gender: Gender.男,
}
类型断言
有时候你会比 TS 更加明确一个值的类型,此时可以使用类型断言来指定更具体的类型,比如根据 ID 选择 a 标签。
// 注意 document.querySelector('a') 这种写法会自动推断出是 HTMLLinkElement 类型
const oLink = document.getElementById('link')
- 注意:该方法返回的类型是 HTMLElement,该类型只包含所有标签公共的属性或方法,不包含 a 标签特有的 href 等属性,这个类型太宽泛(不具体),无法操作 href 等 a 标签特有的属性或方法。
- 解决方式:这种情况下就需要使用类型断言指定更加具体的类型。
const oLink = document.getElementById('link') as HTMLAnchorElement
-
解释说明。
a,使用
as关键字实现类型断言。b,关键字 as 后面的类型是一个更加具体的类型(HTMLAnchorElement 是 HTMLElement 的子类型)。
c,通过类型断言,oLink 的类型变得更加具体,这样就可以访问 a 标签特有的属性或方法了。
-
另一种语法,使用
<>语法,这种语法形式不常用知道即可。
const oLink = <HTMLAnchorElement>document.getElementById('link')
- 技巧:打开浏览器控制台,选中标签,通过
$0.__proto__可以获取 DOM 元素的类型。
🤔 注意:只有两个有“关系”的类型间才能进行断言,例如你可以将一个联合类型(string|number)断言为其中某一更加具体的类型(number),将一个宽泛的类型(Element)断言为更加具体的类型(HTMLDivElement)。
typeof
- JS 中的 typeof 可以在运行时判断类型,TS 中的 typeof 可以在编译时获取类型。
interface Person {
name: string
age: number
}
const person: Person = { name: 'ifer', age: 18 }
// 获取 person 的类型,得到的就是 Person 接口类型
type p = typeof person
- TS 中 typeof 的使用场景:根据已有变量的值,获取该值的类型,来简化类型书写。
const p = { x: 1, y: 2 }
function formatPoint(point) {} // 没有提示
function formatPoint(point: { x: number; y: number }) {} // 有提示,写法麻烦
// 使用 `typeof` 操作符来获取变量 p 的类型,结果与上面对象字面量的形式相同
function formatPoint(point: typeof p) {} // 推荐
- 注意 typeof 出现在类型注解的位置(参数名称的冒号后面,区别于 JS 代码)。
keyof
作用:获取接口、对象(配合 typeof)、类等的所有属性名组成的联合类型。
// 接口
interface Person {
name: string
age: number
}
type K1 = keyof Person // "name" | "age"
type K2 = keyof Person[] // "length" | "toString" | "pop" | "push" | "concat" | "join"
// 对象(要配合 typeof 才能使用)
const obj = { name: 'ifer', age: 18 }
/* type newobj = typeof obj
type keyofObj = keyof newobj // "name" | "age" */
// 简写
type keyofObj = keyof typeof obj // "name" | "age"
let s1: keyofObj = 'name' // ok
let s2: keyofObj = 'xxx' // error
下面的代码了解即可。
// 类
class User {
// constructor(public username: string, public age: number) {}
public username: string
public age: number
constructor(username: string, age: number) {
this.username = username
this.age = age
}
}
type UserInfo = keyof User // "username" | "age"
const s: UserInfo = 'username' // ok
// 基本类型
type K1 = keyof boolean // 'valueOf'
type T2 = keyof number // 'toString' | 'toFixed' | ...
type T3 = keyof any // string | number | symbol
// 枚举
enum HttpMethod {
GET,
POST,
}
type Method = keyof typeof HttpMethod // 'GET' | 'POST'
特殊类型
any
- 原则:不推荐使用 any!这会让 TypeScript 变为 “AnyScript”(失去 TS 类型保护的优势)。
- 因为当值的类型为 any 时,可以对该值进行任意操作,即使可能存在错误,并且不会有代码提示。
let num: any = 8 // 任意类型,不对类型进行校验
num.toFixed() // 没有提示
num = 'xxx' // 可以赋任意值(即可以把任意值给 any 类型)
-
尽可能的避免使用 any 类型,除非临时使用 any 来“避免”书写很长、很复杂的类型,或者有些参数就是可以使用任何类型,例如
console.log()。 -
其他隐式具有 any 类型的情况(因为不推荐使用 any,所以下面两种情况下都应该提供类型)。
a,声明变量不提供类型也不提供默认值。
b,函数参数不加类型。
unknow
- unknown: 任意类型,更安全的 any 类型。
let num: unknown = 88
num = 'abc'
console.log(num)
num() // error: 不能调用方法
console.log(num.length) // error: 不能访问属性
- 可以使用类型收窄来处理 unknown 类型。
let num: unknown = 88
if (typeof num === 'string') {
console.log(num.length)
} else if (typeof num === 'function') {
num()
}
并不是所有的类型都可以进行收窄。
let num = 'hello' // num 的类型已经确定就是 string 类型
if (typeof num === 'string') {
console.log(num.length)
} else if (typeof num === 'function') {
// 如果再等于了 function 类型,那是不可能的,所以 num 被推断为了 never 类型
num() // Error
}
- unknown 类型可以配合断言使用。
let num: unknown = 88
let len = (num as string).length
console.log(len)
比较
- 任何类型可以给 any,any 也可以给任何类型。
let temp: any = 'hello'
let str: string = temp // ok
- 任何类型可以给 unknown,unknown 只能给 unknown 或 any 类型。
let temp: unknown = 'hello'
// 把一个不知道的类型给了 string 类型的变量 str
// let str: string = temp // error
// 解决,配合类型断言
let str: string = temp as string // ok
- 测试:如何把 string 类型的变量赋值给 number 类型?
let temp: string = '888'
// 把 string 类型的变量给了 number 类型的变量 num,显然是有问题的
let num: number = temp
- 解决方式一。
let temp: string = '888'
// 先断言为 any,利用 any 可以给任何类型的特点
let num: number = temp as any
- 解决方式二。
let temp: string = '888'
// 不能直接断言 string 为 number,但可以断言 unknown 为 number
let num: number = temp as unknown as number
never
不可能实现的类型,例如下面的 Test 就是 never。
type Test = number & string
// 也可以当做函数的返回值,表示不会执行到头
function test(): never {
throw new Error('Error')
}
null 和 undefined
let str: string = 'ifer'
// 默认情况下,tsconfig.json 中的 strictNullChecks 的值为 false
// undefined 和 null 是其他类型的子类型,也就是可以作为其他类型的值存在
str = undefined
str = null
函数重载
function greet(name: string): string {
return `Hello ${name}`
}
需求:改造上面的函数,输入 ['a', 'b', 'c'],输出 ['Hello a', 'Hello b', 'Hello c']。
方法 1,使用联合类型实现。
function greet(name: string | string[]): string | string[] {
if (typeof name === 'string') {
return `Hello ${name}`
} else if (Array.isArray(name)) {
return name.map((name) => `Hello ${name}`)
}
throw new Error('异常')
}
const r = greet(['a', 'b', 'c'])
console.log(r) // r 是一个联合类型
// 期望是 string[] 类型,可以通过断言
// const len = (r as string[]).length
// console.log(len)
// 了解
// 泛型断言
// const len = (<string[]>r).length
// console.log(len)
// or
// const len = (<Array<string>>r).length
// console.log(len)
方法 2,使用函数重载实现。
// 一个函数可以有多个重载签名
// !重载签名:包含了函数的参数类型和返回值类型,但不包含函数体
function greet(name: string): string
function greet(name: string[]): string[]
// 一个函数只能有一个实现签名
// !实现签名:参数和返回值要覆盖上面的情况(更通用),且包含了函数体
function greet(person: unknown): unknown {
if (typeof name === 'string') {
return `Hello ${name}`
} else if (Array.isArray(name)) {
return name.map((name) => `Hello ${name}`)
}
throw new Error('异常')
}
console.log(greet(['a', 'b', 'c']))
- 监听 channel.active,发起请求并渲染,
src/components/NewsList.vue。
<script lang="ts" setup>
import { watch } from 'vue'
import useStore from '../store'
const { news, channel } = useStore()
watch(
() => channel.active,
() => {
news.getArticleList(channel.active)
}
)
</script>