**目标:**能够理解什么是ts的类型注解
内容:
示例代码:
let age: number = 18
代码中的
: number
就是类型注解
// 错误演示
// 错误原因:将 string 类型的值赋值给了 number 类型的变量,类型不一致
let age: number = '18'
**目标:**能够理解ts中原始类型的使用
内容:
可以将 TS 中的常用基础类型细分为两类:1 JS 已有类型 2 TS 新增类型
JS 已有类型
原始类型:number/string/boolean/null/undefined/symbol
对象类型:object
(包括,数组、对象、函数等对象)
TS 新增类型
首先,我们先来看原始类型:
let age: number = 18
let myName: string = '黑马程序员'
let isLoading: boolean = false
let nullValue: null = null
let undefinedValue: undefined = undefined
// 特点:Symbol() 创建的值是唯一的
// 作用:可以作为对象属性的键,防止与对象中其他键冲突
let uniqKey: symbol = Symbol()
let obj = {
a: '123',
[uniqKey]: 100,
}
// 取值:
console.log(obj[uniqKey])
**目标:**掌握ts中数组类型的两种写法
内容:
number[]
写法// 写法一:(推荐)
let numbers: number[] = [1, 3, 5]
// 写法二:
let strings: Array<string> = ['a', 'b', 'c']
**目标:**能够通过联合类型将多个类型组合成一个类型
内容:
需求:数组中既有 number 类型,又有 string 类型,这个数组的类型应该如何写?
// 此处 () 的目的是为了将提升优先级,表示:number 类型的数组或 string 类型的数组
let arr: (number | string)[] = [1, 'a', 3, 'b']
|
(竖线)在 TS 中叫做联合类型,即:由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种// 思考:该类型的含义?
let arr: number | string[]
**目标:**能够使用类型别名给类型起别名
内容:
类型别名(自定义类型)
:为任意类型起别名type CustomArray = (number | string)[]
let arr1: CustomArray = [1, 'a', 3, 'b']
let arr2: CustomArray = ['x', 'y', 6, 7]
type
关键字来创建自定义类型**目标:**能够给函数指定类型
内容:
函数参数
和返回值
的类型// 函数声明
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
// 使用自定义类型作为函数 add 的类型
const add: AddFn = (num1, num2) => {
return num1 + num2
}
**目标:**能够了解void类型的使用
内容:
void
function greet(name: string): void {
console.log('Hello', name)
}
void
类型// 如果什么都不写,此时,add 函数的返回值类型为: void
const add = () => {}
// 这种写法是明确指定函数返回值类型为 void,与上面不指定返回值类型相同
const add = (): void => {}
// 但,如果指定 返回值类型为 undefined,此时,函数体中必须显示的 return undefined 才可以
const add = (): undefined => {
// 此处,返回的 undefined 是 JS 中的一个值
return undefined
}
**目标:**能够给函数设置可选参数类型
内容:
slice()
也可以 slice(1)
还可以 slice(1, 3)
function mySlice(start?: number, end?: number): void {
console.log('起始索引:', start, '结束索引:', end)
}
?
(问号)**目标:**掌握对象类型的基本使用
内容:
// 空对象
let person: {} = {}
// 有属性的对象
let person: { name: string } = {
name: '黑马程序员'
}
// 既有属性又有方法的对象
// 在一行代码中指定对象的多个属性类型时,使用 `;`(分号)来分隔
let person: { name: string; sayHi(name: string): void } = {
name: 'jack',
sayHi(name) {}
}
// 对象中如果有多个类型,可以换行写:
// 通过换行来分隔多个属性类型,可以去掉 `;`
let person: {
name: string
sayHi(): void
} = {
name: 'jack',
sayHi() {}
}
{}
来描述对象结构属性名: 类型
的形式方法名(参数: 参数的类型): 返回值类型
的形式{}
形式为对象添加类型,会降低代码的可读性(不好辨识类型和值)// 创建类型别名
type Person = {
name: string
sayHi(): void
}
// 使用类型别名作为对象的类型:
let person: Person = {
name: 'jack',
sayHi() {}
}
type Person = {
greet(name: string): void
}
let person: Person = {
greet(name) {
console.log(name)
}
}
type Person = {
greet: (name: string) => void
}
let person: Person = {
greet(name) {
console.log(name)
}
}
axios({ ... })
时,如果发送 GET 请求,method 属性就可以省略?
来表示type Config = {
url: string
method?: string
}
function myAxios(config: Config) {
console.log(config)
}
// 创建一个对象:学生对象,该对象中具有以下属性和方法:
// 1 属性:姓名、性别、成绩、身高
// 2 方法:学习、打游戏
目标:能够使用接口类型来为对象指定类型
内容:
当一个对象类型被多次使用时,也可以使用接口(
interface
)来描述对象的类型,达到复用的目的
解释:
使用 interface
关键字来声明接口
接口名称(比如,此处的 IPerson),可以是任意合法的变量名称,推荐以 I
开头
声明接口后,直接使用接口名称作为变量的类型
因为每一行只有一个属性类型,因此,属性类型后没有 ;(分号)
interface IPerson {
name: string
age: number
sayHi(): void
}
let person: IPerson = {
name: 'jack',
age: 19,
sayHi() {}
}
目标:能够理解interface和type的相同点和不同点
内容:
interface(接口)和 type(类型别名)的对比:
注意:interface 和 type 在使用上还有其他的不同之处,请参考文档说明
约定:能使用 type 就是用 type
interface IPerson {
name: string
age: number
sayHi(): void
}
// 为对象类型创建类型别名
type IPerson = {
name: string
age: number
sayHi(): void
}
// 为联合类型创建类型别名
type NumStr = number | string
目标:能够通过接口继承实现复用
内容:
如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用
比如,这两个接口都有 x、y 两个属性,重复写两次,可以,但很繁琐
interface Point2D { x: number; y: number }
interface Point3D { x: number; y: number; z: number }
使用接口继承,来简化:
extends
(继承)关键字实现了接口 Point3D 继承 Point2Dinterface Point2D { x: number; y: number }
// 继承 Point2D
interface Point3D extends Point2D {
z: number
}
// 继承后 Point3D 的结构:{ x: number; y: number; z: number }
目标:能够使用交叉类型模拟接口继承的功能
内容:
&
,交叉类型(intersection types)// 使用 type 自定义类型来模拟 Point2D 和 Point3D
type Point2D = {
x: number
y: number
}
// 使用 交叉类型 来实现接口继承的功能:
// 使用 交叉类型 后,Point3D => { x: number; y: number; z: number }
// 也就是同时具有了 Point2D 和 后面对象 的所有的属性了
type Point3D = Point2D & {
z: number
}
let o: Point3D = {
x: 1,
y: 2,
z: 3
}
目标:能够理解什么是元组
内容:
场景:在地图中,使用经纬度坐标来标记位置信息
可以使用数组来记录坐标,那么,该数组中只有两个元素,并且这两个元素都是数值类型
使用 number[] 的缺点:不严谨,因为该类型的数组中可以出现任意多个数字
let position: number[] = [116.2317, 39.5427]
更好的方式:元组 Tuple
// 该示例中,元素有两个元素,每个元素的类型都是 number
let position: [number, number] = [116.2317, 39.5427]
实际上,React 中 useState
hook 的返回值类型就是一个元组类型
// 因为对于 useState 来说,它的返回值长度固定切每个索引对应的元素类型也是知道的
const [loading, setLoading] = useState(false)
目标:能够知道什么是TS的类型推论
内容:
在 TS 中,某些没有明确指出类型的地方,TS 的类型推论机制会帮助提供类型
// 变量 age 的类型被自动推断为:number
let age = 18
// 函数返回值的类型被自动推断为:number
// 注意:函数参数一定要添加类型
function add(num1: number, num2: number) {
return num1 + num2
}
目标:能够知道TS的字面量类型是什么
内容:
思考以下代码,两个变量的类型分别是什么?
let str1 = 'Hello TS'
const str2 = 'Hello TS'
通过 TS 类型推论机制(技巧),可以得到答案:
变量 str1 的类型为:string
变量 str2 的类型为:‘Hello TS’
解释:
注意:此处的 ‘Hello TS’,就是一个字面量类型,也就是说某个特定的字符串也可以作为 TS 中的类型
实际上,任意的 JS 字面量(比如,对象、数字等)都可以作为类型使用
// 字面量类型:{ name: 'jack' }
const obj: { name: 'jack' } = { name: 'jack' }
// 字面量类型:[]
const arr: [] = []
// 字面量类型:18
const age: 18 = 18
// 字面量类型:false
const falseValue: false = false
// [1, 3, 5] 就是一个字面量值
const arr = [1, 3, 5]
const arr1 = new Array()
// { name: 'jack' } 也是一个字面量值
const obj = { name: 'jack' }
const _obj = new Object()
目标:能够用字面量类型配合联合类型一起使用
内容:
使用模式:字面量类型配合联合类型一起使用,用来表示一组明确的可选值列表
// 使用自定义类型:
type Direction = 'up' | 'down' | 'left' | 'right'
function changeDirection(direction: Direction) {
console.log(direction)
}
// 调用函数时,会有类型提示:
changeDirection('up')
up/down/left/right
中的任意一个目标:能够了解TS中的枚举类型
内容:
枚举:使用enum定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个
枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值
// 创建枚举类型
enum Direction { Up, Down, Left, Right }
// 使用枚举类型
function changeDirection(direction: Direction) {
console.log(direction)
}
// 调用函数时,需要应该传入:枚举 Direction 成员的任意一个
// 类似于 JS 中的对象,直接通过 点(.)语法 访问枚举的成员
changeDirection(Direction.Up)
解释:
使用 enum
关键字定义枚举
约定枚举名称以大写字母开头
枚举中的多个值之间通过 ,
(逗号)分隔
定义好枚举后,直接使用枚举名称作为类型注解
目标:能够了解什么是数字枚举
内容:
问题:我们把枚举成员作为了函数的实参,它的值是什么呢?
数字枚举
// Down -> 11、Left -> 12、Right -> 13
enum Direction { Up = 10, Down, Left, Right }
enum Direction { Up = 2, Down = 4, Left = 8, Right = 16 }
目标:能够了解什么是字符串枚举
内容:
字符串枚举:枚举成员的值是字符串
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
目标:能够了解枚举类型的实现原理
内容:
枚举是 TS 为数不多的非 JavaScript 类型级扩展(不仅仅是类型)的特性之一
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
// 会被编译为以下 JS 代码:
// 整体来看:将枚举转化成了 JS 中的一个对象
/*
var Direction = {
Up: 'UP',
Down: 'DOWN',
Left: 'LEFT',
Right: 'RIGHT'
}
*/
var Direction;
(function (Direction) {
Direction["Up"] = "UP";
Direction["Down"] = "DOWN";
Direction["Left"] = "LEFT";
Direction["Right"] = "RIGHT";
})(Direction || (Direction = {}));
目标:能够使用TS中的类型断言指定更加具体的类型
内容:
有时候你会比 TS 更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型。 比如,
// 假设页面中有个 id 为 link 的 a 标签:
// 传智播客
// 我们希望通过 DOM 拿到 a 标签的 href 属性
const aLink = document.getElementById('a')
使用类型断言:
as
关键字实现类型断言// 使用类型断言来指定为更加具体的 HTMLAnchorElement 类型
const aLink = document.getElementById('link') as HTMLAnchorElement
<>
语法,这种语法形式不常用知道即可:// 该语法,知道即可:
const aLink = <HTMLAnchorElement>document.getElementById('link')
技巧:在浏览器控制台,通过 __proto__
可以获取 DOM 元素的类型
目标:能够知道TS中的typeof运算符的作用
内容:
众所周知,JS 中提供了 typeof 操作符,用来在 JS 中获取数据的类型
console.log(typeof 'Hello world') // ? string
实际上,TS 也提供了 typeof 操作符:可以在类型上下文中引用变量或属性的类型(类型查询)
let p = { x: 1, y: 2 }
function formatPoint1(point: { x: number; y: number }) {}
formatPoint1(p)
// 此处使用 typeof p 获取变量 p 在 TS 中的类型:
function formatPoint2(point: typeof p) {}
formatPoint2({ x: 1, y: 2 })
typeof
操作符来获取变量 p 的类型,结果与第一种(对象字面量形式的类型)相同目标:能够知道TS中的any类型
内容:
原则:不推荐使用 any!这会让 TypeScript 变为 “AnyScript”(失去 TS 类型保护的优势)
let obj: any = { x: 0 }
obj.bar = 100
obj()
const n: number = obj
说明:尽可能的避免使用 any 类型,除非临时使用 any 来“避免”书写很长、很复杂的类型
其他隐式具有 any 类型的情况
声明变量不提供类型也不提供默认值
函数参数不加类型
注意:因为不推荐使用 any,所以,这两种情况下都应该提供类型
目标:能够知道泛型的作用
内容:
泛型(Generics)可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class 中
// 比如,该函数传入什么数值,就返回什么数值
function id(value: number): number { return value }
// res => 10
const res = id(10)
function id(value: any): any { return value }
目标:能够使用泛型创建一个基本的泛型函数
内容:
- 创建泛型函数:函数名(参数:Type):Type{}
function id<Type>(value: Type): Type { return value }
// 也可以仅使用一个字母来作为类型变量的名称
function id<T>(value: T): T { return value }
解释:
语法:在函数名称的后面添加 <>
(尖括号),尖括号中添加类型变量,比如此处的 Type
类型变量 Type,是一种特殊类型的变量,它处理类型而不是值(指代)
类型变量相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)
因为 Type 是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型
类型变量 Type,可以是任意合法的变量名称
- 调用泛型函数:函数名<类型>(实参)
// 函数参数和返回值类型都为:number
const num = id<number>(10)
// 函数参数和返回值类型都为:string
const str = id<string>('a')
解释:
语法:在函数名称的后面添加 <>
(尖括号),尖括号中指定具体的类型,比如,此处的 number
当传入类型 number 后,这个类型就会被函数声明时指定的类型变量 Type 捕获到
此时,Type 的类型就是 number,所以,函数 id 参数和返回值的类型也都是 number
这样,通过泛型就做到了让 id 函数与多种不同的类型一起工作,实现了复用的同时保证了类型安全
目标:能够知道在调用泛型函数时可以省略尖括号
内容:
在调用泛型函数时,可以省略
<类型>
来简化泛型函数的调用
// 省略 调用函数
let num = id(10)
let str = id('a')
解释:
此时,TS 内部会采用一种叫做类型参数推断的机制,来根据传入的实参自动推断出类型变量 Type 的类型
比如,传入实参 10,TS 会自动推断出变量 num 的类型 number,并作为 Type 的类型
推荐:使用这种简化的方式调用泛型函数,使代码更短,更易于阅读
说明:当编译器无法推断类型或者推断的类型不准确时,就需要显式地传入类型参数
目标:能够知道为什么要为泛型添加约束
内容:
默认情况下,泛型函数的类型变量 Type 可以代表任意类型,这导致无法访问任何属性
比如,以下示例代码中想要获取参数的长度:
function id<Type>(value: Type): Type {
// 注意:此处会报错
console.log(value.length)
return value
}
id('a')
此时,就需要为泛型添加约束来
收缩类型
(缩窄类型取值范围)
添加泛型约束收缩类型,主要有以下两种方式:1 指定更加具体的类型 2 添加约束
首先,我们先来看第一种情况,如何指定更加具体的类型:
比如,将类型修改为 Type[]
(Type 类型的数组),因为只要是数组就一定存在 length 属性,因此就可以访问了
function id<Type>(value: Type[]): Type[] {
// 可以正确访问
console.log(value.length)
return value
}
目标:能够使用extends关键字来为泛型函数添加类型约束
内容:
// 创建一个自定义类型
interface ILength { length: number }
// Type extends ILength 添加泛型约束
// 解释:表示传入的类型必须满足 ILength 接口的要求才行,也就是得有一个 number 类型的 length 属性
function id<Type extends ILength>(value: Type): Type {
console.log(value.length)
return value
}
解释:
创建描述约束的接口 ILength,该接口要求提供 length 属性
通过 extends
关键字来为泛型(类型变量)添加约束
该约束表示:传入的类型必须具有 length 属性
注意:传入的实参(比如,数组)只要有 length 属性即可(类型兼容性)
目标:能够知道泛型可以有多个类型变量
内容:
泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)
比如,创建一个函数来获取对象中属性的值:
function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key]
}
let person = { name: 'jack', age: 18 }
getProp(person, 'name')
解释:
,
逗号分隔。keyof Type
实际上获取的是 person 对象所有键的联合类型,也就是:'name' | 'age'
目标:能够为接口添加泛型
内容:
接口也可以配合泛型来使用,以增加其灵活性,增强其复用性
interface IdFunc<Type> {
id: (value: Type) => Type
ids: () => Type[]
}
let obj: IdFunc<number> = {
id(value) { return value },
ids() { return [1, 3, 5] }
}
解释:
<类型变量>
,那么,这个接口就变成了泛型接口。实际上,JS 中的数组在 TS 中就是一个泛型接口
const strs = ['a', 'b', 'c']
// 鼠标放在 forEach 上查看类型
strs.forEach
const nums = [1, 3, 5]
// 鼠标放在 forEach 上查看类型
nums.forEach
目标:了解常用泛型工具类型
内容:TS 内置了一些常用的工具类型,来简化 TS 中的一些常见操作
说明:它们都是基于泛型实现的(泛型适用于多种类型,更加通用),并且是内置的,可以直接在代码中使用。 这些工具类型有很多,主要学习以下几个:
Partial
Readonly
Pick
Partial 用来构造(创建)一个类型,将 Type 的所有属性设置为可选。
type Props = {
id: string
children: number[]
}
type PartialProps = Partial<Props>
Readonly 用来构造一个类型,将 Type 的所有属性都设置为 readonly(只读)。
type Props = {
id: string
children: number[]
}
type ReadonlyProps = Readonly<Props>
let props: ReadonlyProps = { id: '1', children: [] }
// 错误演示
props.id = '2'
Pick
从 Type 中选择一组属性来构造新类型。
interface Props {
id: string
title: string
children: number[]
}
type PickProps = Pick<Props, 'id' | 'title'>