传说中的t~s

102 阅读23分钟

1.TypeScrip用处

TypeScript官方文档 是这样介绍的

TypeScript具有类型系统,且是JavaScript的超集。 它可以编译成普通的JavaScript代码。 TypeScript支持任意浏览器,任意环境,任意系统并且是开源的。

2.为什么有了JavaScript还会有TypeScrip呢?

那就从三个方面来讲

Ts对比Js

  1. ts是js的超集,存在类型的脚本语言
  2. 继承了js的所有编程类型,js代码可在ts环境很好的运行
  3. 为构建大型应用而生,但小程序同样适用
  4. 强大的类型系统,拥有静态类型检查能力
  5. 新增类型注解和类型推断
  6. 拥有丰富的class扩展功能
  7. 添加了系统级设计能力,设计模式由顶层由下进行设计

Js和Ts的区别

JSTS
运行时类型静态类型
程序运行时进行类型检查编译期进行类型检查
开发环境无法提供给帮助开发环境能提供丰富的信息
需要进行大量测试,消耗时间覆盖测试场景,才能保证代码的覆盖率大部分检查有语言自身完成
class扩展较弱拥有很强class扩展
仅支持版本等级较高浏览器支持所有的浏览器
无访问控制权限拥有完整的成员访问控制权限
无高级特性静态成员等特性

Js和Ts的不同系统设计能力

JSTS
完备的系统设计能力
拥有interface接口规范
拥有abstrace抽象类

3.TypeScrip有哪些常用类型呢?

1. 类型注解

   示例代码:

let age = 18  
let age: number = 18
复制代码
  • 说明:代码中的 : number 就是类型注解
  • 作用:为变量添加类型约束。比如,上述代码中,约定变量 age 的类型为 number 类型
  • 解释:约定了什么类型,就只能给变量赋值该类型的值,否则,就会报错
  • 约定了类型之后,代码的提示就会非常的清晰
  • 错误演示:
// 错误代码:
// 错误原因:将 string 类型的值赋值给了 number 类型的变量,类型不一致
let age: number = '18'
复制代码

2.原始类型

可以将 TS 中的常用基础类型细分为两类:1 JS 已有类型 2 TS 新增类型

  1. JS 已有类型

    • 原始类型:number/string/boolean/null/undefined
    • 对象类型:object(包括,数组、对象、函数等对象)
  2. TS 新增类型

    • 联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any 等
  • 注意:

    1. 原始类型在 TS 和 JS 中写法一致
    2. 对象类型在 TS 中更加细化,每个具体的对象(比如,数组、对象、函数)都有自己的类型语法
  • 原始类型:number/string/boolean/null/undefined

  • 特点:简单,这些类型,完全按照 JS 中类型的名称来书写 示例代码:

// 数字类型
let num: number = 19// 字符串类型
let userName: string = '张三'// 布尔类型
let isLoading: boolean = true// null类型
let nu: null = null// undefined类型
let un: undefined = undefined
复制代码

3.数组类型

  • 数组类型的两种写法:

    • 推荐使用 number[] 写法 示例代码:
// 写法一:
let numbers: number[] = [1, 3, 5]
// 写法二:
let strings: Array<string> = ['a', 'b', 'c']
复制代码

4.联合类型

需求:数组中既有 number 类型,又有 string 类型,这个数组的类型应该如何写?

let arr: (number | string)[] = [1, 'a', 3, 'b']
复制代码
  • 解释:|(竖线)在 TS 中叫做联合类型,即:由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种
  • 注意:这是 TS 中联合类型的语法,只有一根竖线,不要与 JS 中的或(|| 或)混淆了 示例代码:
  let timer: number | null = null
  timer = setInterval(() => {}, 1000)
​
  // 定义一个数组,数组中可以有数字或者字符串, 需要注意 | 的优先级
  let arr: (number | string)[] = [1, 'abc', 2]
复制代码

5.类型别名

  • 类型别名(自定义类型):为任意类型起别名
  • 使用场景:当同一类型(复杂)被多次使用时,可以通过类型别名,简化该类型的使用
type CustomArray = (number | string)[]
​
let arr1: CustomArray = [1, 'a', 3, 'b']
let arr2: CustomArray = ['x', 'y', 6, 7]
复制代码
  • 解释:

    1. 使用 type 关键字来创建自定义类型
    2. 类型别名(比如,此处的 CustomArray)可以是任意合法的变量名称
    3. 推荐使用大写字母开头
    4. 创建类型别名后,直接使用该类型别名作为变量的类型注解即可

6.函数类型-基本使用

  • 函数的类型实际上指的是:函数参数返回值的类型

  • 为函数指定类型的两种方式:

    1. 单独指定参数、返回值的类型
    2. 同时指定参数、返回值的类型
  1. 单独指定参数、返回值的类型:
// 函数声明
function add(num1: number, num2: number): number {
  return num1 + num2
}
​
// 箭头函数
const add = (num1: number, num2: number): number => {
  return num1 + num2
}
复制代码
  1. 同时指定参数、返回值的类型:
type AddFn = (num1: number, num2: number) => numberconst add: AddFn = (num1, num2) => {
  return num1 + num2
}
复制代码
  • 解释:当函数作为表达式时,可以通过类似箭头函数形式的语法来为函数添加类型
  • 注意:这种形式只适用于函数表达式

7.函数类型-void 类型

  • 如果函数没有返回值,那么,函数返回值类型为:void
function greet(name: string): void {
  console.log('Hello', name)
}
复制代码
  • 注意:

    • 如果一个函数没有返回值,此时,在 TS 的类型中,应该使用 void 类型
// 如果什么都不写,此时,add 函数的返回值类型为: void
const add = () => {}
// 这种写法是明确指定函数返回值类型为 void,与上面不指定返回值类型相同
const add = (): void => {}
​
// 但,如果指定 返回值类型为 undefined,此时,函数体中必须显示的 return undefined 才可以
const add = (): undefined => {
  // 此处,返回的 undefined 是 JS 中的一个值
  return undefined
}
复制代码

8.函数类型-可选参数

  • 使用函数实现某个功能时,参数可以传也可以不传。这种情况下,在给函数参数指定类型时,就用到可选参数
  • 比如,数组的 slice 方法,可以 slice() 也可以 slice(1) 还可以 slice(1, 3)
function mySlice(start?: number, end?: number): void {
  console.log('起始索引:', start, '结束索引:', end)
}
复制代码
  • 可选参数:在可传可不传的参数名称后面添加 ?(问号)
  • 注意:可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数

9.函数类型-参数默认值

  • 参数默认值和可选参数互斥的,一般只需要指定一种即可。
function mySlice(start: number = 0, end: number = 10): void {
  console.log('起始索引:', start, '结束索引:', end)
}
复制代码

10.对象类型-基本使用

  • JS 中的对象是由属性和方法构成的,而 TS 对象的类型就是在描述对象的结构(有什么类型的属性和方法)
  • 对象类型的写法:
// 空对象
let person: {} = {}
​
// 有属性的对象
let person: { name: string } = {
  name: '同学'
}
​
// 既有属性又有方法的对象
// 在一行代码中指定对象的多个属性类型时,使用 `;`(分号)来分隔
let person: { name: string; sayHi(): void } = {
  name: 'jack',
  sayHi() {}
}
​
// 对象中如果有多个类型,可以换行写:
// 通过换行来分隔多个属性类型,可以去掉 `;`
let person: {
  name: string
  sayHi(): void
} = {
  name: 'jack',
  sayHi() {}
}
复制代码
  • 解释:

    1. 使用 {} 来描述对象结构
    2. 属性采用属性名: 类型的形式
    3. 方法采用方法名(): 返回值类型的形式

11.对象类型-带有参数的方法类型

  • 如果方法有参数,就在方法名后面的小括号中指定参数类型
type Person = {
  greet(name: string): void
}
​
let person: Person = {
  greet(name) {
    console.log(name)
  }
}
复制代码

12.对象类型-箭头函数形式的方法类型

  • 方法的类型也可以使用箭头函数形式
type Person = {
  greet: (name: string) => void
}
​
let person: Person = {
  greet(name) {
    console.log(name)
  }
}
复制代码

13.对象类型-对象可选属性

  • 对象的属性或方法,也可以是可选的,此时就用到可选属性
  • 比如,我们在使用 axios({ ... }) 时,如果发送 GET 请求,method 属性就可以省略
  • 可选属性的语法与函数可选参数的语法一致,都使用 ? 来表示
type Config = {
  url: string
  method?: string
}
​
function myAxios(config: Config) {
  console.log(config)
}
复制代码

14.对象类型-使用类型别名

  • 注意:直接使用 {} 形式为对象添加类型,会降低代码的可读性(不好辨识类型和值)
  • 推荐:使用类型别名为对象添加类型
// 创建类型别名
type Person = {
  name: string
  sayHi(): void
}
​
// 使用类型别名作为对象的类型:
let person: Person = {
  name: 'jack',
  sayHi() {}
}
复制代码

15.对象类型-接口

当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的

  • 解释:

    1. 使用 interface 关键字来声明接口
    2. 接口名称(比如,此处的 IPerson),可以是任意合法的变量名称,推荐以 I 开头
    3. 声明接口后,直接使用接口名称作为变量的类型
    4. 因为每一行只有一个属性类型,因此,属性类型后没有 ;(分号)
interface IPerson {
  name: string
  age: number
  sayHi(): void
}
​
let person: IPerson = {
  name: 'jack',
  age: 19,
  sayHi() {}
}
复制代码

16.对象类型-interface vs 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
复制代码

17.对象类型-接口继承

  • 如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用
  • 比如,这两个接口都有 x、y 两个属性,重复写两次,可以,但很繁琐
interface Point2D { x: number; y: number }
interface Point3D { x: number; y: number; z: number }
复制代码
  • 更好的方式:
interface Point2D { x: number; y: number }
// 继承 Point2D
interface Point3D extends Point2D {
  z: number
}
复制代码
  • 解释:

    1. 使用 extends(继承)关键字实现了接口 Point3D 继承 Point2D
    2. 继承后,Point3D 就有了 Point2D 的所有属性和方法(此时,Point3D 同时有 x、y、z 三个属性)

18.元组类型

  • 场景:在地图中,使用经纬度坐标来标记位置信息
  • 可以使用数组来记录坐标,那么,该数组中只有两个元素,并且这两个元素都是数值类型
let position: number[] = [116.2317, 39.5427]
复制代码
  • 使用 number[] 的缺点:不严谨,因为该类型的数组中可以出现任意多个数字
  • 更好的方式:元组 Tuple
  • 元组类型是另一种类型的数组,它确切地知道包含多少个元素,以及特定索引对应的类型
let position: [number, number] = [39.5427, 116.2317]
复制代码
  • 解释:

    1. 元组类型可以确切地标记出有多少个元素,以及每个元素的类型
    2. 该示例中,元素有两个元素,每个元素的类型都是 number

19.类型推论

  • 在 TS 中,某些没有明确指出类型的地方,TS 的类型推论机制会帮助提供类型

  • 换句话说:由于类型推论的存在,这些地方,类型注解可以省略不写

  • 发生类型推论的 2 种常见场景:

    1. 声明变量并初始化时
    2. 决定函数返回值时
// 变量 age 的类型被自动推断为:number
let age = 18// 函数返回值的类型被自动推断为:number
function add(num1: number, num2: number) {
  return num1 + num2
}
复制代码
  • 推荐:能省略类型注解的地方就省略偷懒,充分利用TS类型推论的能力,提升开发效率)
  • 技巧:如果不知道类型,可以通过鼠标放在变量名称上,利用 VSCode 的提示来查看类型
  • 推荐:在 VSCode 中写代码的时候,多看方法、属性的类型,养成写代码看类型的习惯

20.字面量类型-基本使用

  • 思考以下代码,两个变量的类型分别是什么?
let str1 = 'Hello TS'
const str2 = 'Hello TS'
复制代码
  • 通过 TS 类型推论机制,可以得到答案:

    1. 变量 str1 的类型为:string
    2. 变量 str2 的类型为:'Hello TS'
  • 解释:

  1. str1 是一个变量(let),它的值可以是任意字符串,所以类型为:string
  2. str2 是一个常量(const),它的值不能变化只能是 'Hello TS',所以,它的类型为:'Hello TS'
  • 注意:此处的 'Hello TS',就是一个字面量类型,也就是说某个特定的字符串也可以作为 TS 中的类型

  • 任意的 JS 字面量(比如,对象、数字等)都可以作为类型使用

    • 字面量:{ name: 'jack' } [] 18 20 'abc' false function() {}

21.字面量类型-使用模式和场景

  • 使用模式:字面量类型配合联合类型一起使用
  • 使用场景:用来表示一组明确的可选值列表
  • 比如,在贪吃蛇游戏中,游戏的方向的可选值只能是上、下、左、右中的任意一个
// 使用自定义类型:
type Direction = 'up' | 'down' | 'left' | 'right'function changeDirection(direction: Direction) {
  console.log(direction)
}
​
// 调用函数时,会有类型提示:
changeDirection('up')
复制代码
  • 解释:参数 direction 的值只能是 up/down/left/right 中的任意一个
  • 优势:相比于 string 类型,使用字面量类型更加精确、严谨

22.枚举类型-基本使用(了解)

  • 枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值
  • 枚举:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个
// 创建枚举
enum Direction { Up, Down, Left, Right }
​
// 使用枚举类型
function changeDirection(direction: Direction) {
  console.log(direction)
}
​
// 调用函数时,需要应该传入:枚举 Direction 成员的任意一个
// 类似于 JS 中的对象,直接通过 点(.)语法 访问枚举的成员
changeDirection(Direction.Up)
复制代码
  • 解释:

    1. 使用 enum 关键字定义枚举
    2. 约定枚举名称以大写字母开头
    3. 枚举中的多个值之间通过 ,(逗号)分隔
    4. 定义好枚举后,直接使用枚举名称作为类型注解

23.枚举类型-数字枚举

  • 问题:我们把枚举成员作为了函数的实参,它的值是什么呢?
  • 解释:通过将鼠标移入 Direction.Up,可以看到枚举成员 Up 的值为 0
  • 注意:枚举成员是有值的,默认为:从 0 开始自增的数值
  • 我们把,枚举成员的值为数字的枚举,称为:数字枚举
  • 当然,也可以给枚举中的成员初始化值
// Down -> 11Left -> 12Right -> 13
enum Direction { Up = 10, Down, Left, Right }
​
enum Direction { Up = 2, Down = 4, Left = 8, Right = 16 }
复制代码

24.枚举类型-字符串枚举

  • 字符串枚举:枚举成员的值是字符串
  • 注意:字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值
enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT'
}
复制代码

25.枚举类型-枚举实现原理

  • 枚举是 TS 为数不多的非 JavaScript 类型级扩展(不仅仅是类型)的特性之一
  • 因为:其他类型仅仅被当做类型,而枚举不仅用作类型,还提供值(枚举成员都是有值的)
  • 也就是说,其他的类型会在编译为 JS 代码时自动移除。但是,枚举类型会被编译为 JS 代码
enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT'
}
​
// 会被编译为以下 JS 代码:
var Direction;
​
(function (Direction) {
  Direction['Up'] = 'UP'
  Direction['Down'] = 'DOWN'
  Direction['Left'] = 'LEFT'
  Direction['Right'] = 'RIGHT'
})(Direction || Direction = {})
复制代码
  • 说明:枚举与前面讲到的字面量类型+联合类型组合的功能类似,都用来表示一组明确的可选值列表
  • 一般情况下,推荐使用字面量类型+联合类型组合的方式,因为相比枚举,这种方式更加直观、简洁、高效

26.any 类型

  • 原则:不推荐使用 any!这会让 TypeScript 变为 “AnyScript”(失去 TS 类型保护的优势)
  • 因为当值的类型为 any 时,可以对该值进行任意操作,并且不会有代码提示
let obj: any = { x: 0 }
​
obj.bar = 100
obj()
const n: number = obj
复制代码
  • 解释:以上操作都不会有任何类型错误提示,即使可能存在错误

  • 尽可能的避免使用 any 类型,除非临时使用 any 来“避免”书写很长、很复杂的类型

  • 其他隐式具有 any 类型的情况

    1. 声明变量不提供类型也不提供默认值
    2. 函数参数不加类型
  • 注意:因为不推荐使用 any,所以,这两种情况下都应该提供类型

  • Any


27.类型断言

有时候你会比 TS 更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型。 比如,

const aLink = document.getElementById('link')
复制代码
  • 注意:该方法返回值的类型是 HTMLElement,该类型只包含所有标签公共的属性或方法,不包含 a 标签特有的 href 等属性
  • 因此,这个类型太宽泛(不具体) ,无法操作 href 等 a 标签特有的属性或方法
  • 解决方式:这种情况下就需要使用类型断言指定更加具体的类型
  • 使用类型断言:
const aLink = document.getElementById('link') as HTMLAnchorElement
复制代码
  • 解释:

    1. 使用 as 关键字实现类型断言
    2. 关键字 as 后面的类型是一个更加具体的类型(HTMLAnchorElement 是 HTMLElement 的子类型)
    3. 通过类型断言,aLink 的类型变得更加具体,这样就可以访问 a 标签特有的属性或方法了
  • 另一种语法,使用 <> 语法,这种语法形式不常用知道即可:

// 该语法,知道即可:
const aLink = <HTMLAnchorElement>document.getElementById('link')
复制代码

技巧:在浏览器控制台,通过 __proto__ 获取 DOM 元素的类型

function getLength(arg: string | number) {
  if ((arg as string).length) {
    return (arg as string).length
  }
  return arg
}
​
console.log(getLength(2))
复制代码

28.typeof

  • 众所周知,JS 中提供了 typeof 操作符,用来在 JS 中获取数据的类型
console.log(typeof 'Hello world') // ?
复制代码
  • 实际上,TS 也提供了 typeof 操作符:可以在类型上下文中引用变量或属性的类型(类型查询)
  • 使用场景:根据已有变量的值,获取该值的类型,来简化类型书写
const p = {
  x: 100,
  y: 100
}
function getPoint(point: { x: number; y: number }) {
  console.log(point.x)
  console.log(point.y)
}
​
getPoint(p)
复制代码
const p = {
  x: 100,
  y: 100
}
function getPoint(point: typeof p) {
  console.log(point.x)
  console.log(point.y)
}
function getPoint(point: typeof p) {
  console.log(point.x)
  console.log(point.y)
}
​
getPoint(p)
​
复制代码
  • 解释:

    1. 使用 typeof 操作符来获取变量 p 的类型,结果与第一种(对象字面量形式的类型)相同
    2. typeof 出现在类型注解的位置(参数名称的冒号后面)所处的环境就在类型上下文(区别于 JS 代码)
    3. 注意:typeof 只能用来查询变量或属性的类型,无法查询其他形式的类型(比如,函数调用的类型)

4.TypeScrip有哪些高级类型呢?

泛型-基本介绍

  • 泛型是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class 中
  • 需求:创建一个 id 函数,传入什么数据就返回该数据本身(也就是说,参数和返回值类型相同)
function id(value: number): number { return value }
​
复制代码
  • 比如,id(10) 调用以上函数就会直接返回 10 本身。但是,该函数只接收数值类型,无法用于其他类型
  • 为了能让函数能够接受任意类型,可以将参数类型修改为 any。但是,这样就失去了 TS 的类型保护,类型不安全
function id(value: any): any { return value }
复制代码
  • 泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活可复用
  • 实际上,在 C# 和 Java 等编程语言中,泛型都是用来实现可复用组件功能的主要工具之一

1.泛型-泛型函数

定义泛型函数

function fn<Type>(value: Type): Type { return value }function fn<T>(value: T): T { return value }
复制代码
  • 解释:

    1. 语法:在函数名称的后面添加 <>(尖括号),尖括号中添加类型变量,比如此处的 Type
    2. 类型变量 Type,是一种特殊类型的变量,它处理类型而不是值
    3. 该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)
    4. 因为 Type 是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型
    5. 类型变量 Type,可以是任意合法的变量名称

调用泛型函数

const num = fn<number>(10)
const str = fn<string>('a')
复制代码
  • 解释:

    1. 语法:在函数名称的后面添加 <>(尖括号),尖括号中指定具体的类型,比如,此处的 number
    2. 当传入类型 number 后,这个类型就会被函数声明时指定的类型变量 Type 捕获到
    3. 此时,Type 的类型就是 number,所以,函数 id 参数和返回值的类型也都是 number
  • 同样,如果传入类型 string,函数 id 参数和返回值的类型就都是 string

  • 这样,通过泛型就做到了让 id 函数与多种不同的类型一起工作,实现了复用的同时保证了类型安全

2.简化泛型函数调用

// 省略 <number> 调用函数
let num = fn(10)
let str = fn('a')
复制代码
  • 解释:

    1. 在调用泛型函数时,可以省略 <类型> 来简化泛型函数的调用
    2. 此时,TS 内部会采用一种叫做类型参数推断的机制,来根据传入的实参自动推断出类型变量 Type 的类型
    3. 比如,传入实参 10,TS 会自动推断出变量 num 的类型 number,并作为 Type 的类型
  • 推荐:使用这种简化的方式调用泛型函数,使代码更短,更易于阅读

  • 说明:当编译器无法推断类型或者推断的类型不准确时,就需要显式地传入类型参数

3.泛型约束

  • 默认情况下,泛型函数的类型变量 Type 可以代表多个类型,这导致无法访问任何属性
  • 比如,id('a') 调用函数时获取参数的长度:
function fn<Type>(value: Type): Type {
  console.log(value.length)
  return value
}
​
fn('a')
复制代码
  • 解释:Type 可以代表任意类型,无法保证一定存在 length 属性,比如 number 类型就没有 length
  • 此时,就需要为泛型添加约束来收缩类型(缩窄类型取值范围)
  • 添加泛型约束收缩类型,主要有以下两种方式:1 指定更加具体的类型 2 添加约束

4.指定更加具体的类型

比如,将类型修改为 Type[](Type 类型的数组),因为只要是数组就一定存在 length 属性,因此就可以访问了

function fn<Type>(value: Type[]): Type[] {
  console.log(value.length)
  return value
}
复制代码

5.添加约束

// 创建一个接口
interface ILength { length: number }
​
// Type extends ILength 添加泛型约束
// 解释:表示传入的 类型 必须满足 ILength 接口的要求才行,也就是得有一个 number 类型的 length 属性
function id<Type extends ILength>(value: Type): Type {
  console.log(value.length)
  return value
}
复制代码
  • 解释:

    1. 创建描述约束的接口 ILength,该接口要求提供 length 属性
    2. 通过 extends 关键字使用该接口,为泛型(类型变量)添加约束
    3. 该约束表示:传入的类型必须具有 length 属性
  • 注意:传入的实参(比如,数组)只要有 length 属性即可(类型兼容性)

6.泛型-多个类型变量

泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束) 比如,创建一个函数来获取对象中属性的值:

function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key]
}
let person = { name: 'jack', age: 18 }
getProp(person, 'name')
复制代码
  • 解释:

    1. 添加了第二个类型变量 Key,两个类型变量之间使用 , 逗号分隔。
    2. keyof 关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型
    3. 本示例中 keyof Type 实际上获取的是 person 对象所有键的联合类型,也就是:'name' | 'age'
    4. 类型变量 Key 受 Type 约束,可以理解为:Key 只能是 Type 所有键中的任意一个,或者说只能访问对象中存在的属性
// Type extends object 表示: Type 应该是一个对象类型,如果不是 对象 类型,就会报错
// 如果要用到 对象 类型,应该用 object ,而不是 Object
function getProperty<Type extends object, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key]
}
复制代码

7.泛型接口

泛型接口:接口也可以配合泛型来使用,以增加其灵活性,增强其复用性

interface IdFunc<Type> {
  id: (value: Type) => Type
  ids: () => Type[]
}
​
let obj: IdFunc<number> = {
  id(value) { return value },
  ids() { return [1, 3, 5] }
}
复制代码
  • 解释:

    1. 在接口名称的后面添加 <类型变量>,那么,这个接口就变成了泛型接口。
    2. 接口的类型变量,对接口中所有其他成员可见,也就是接口中所有成员都可以使用类型变量
    3. 使用泛型接口时,需要显式指定具体的类型(比如,此处的 IdFunc)。
    4. 此时,id 方法的参数和返回值类型都是 number;ids 方法的返回值类型是 number[]。

8.数组-泛型接口

实际上,JS 中的数组在 TS 中就是一个泛型接口。

const strs = ['a', 'b', 'c']
// 鼠标放在 forEach 上查看类型
strs.forEach
​
const nums = [1, 3, 5]
// 鼠标放在 forEach 上查看类型
nums.forEach
复制代码
  • 解释:当我们在使用数组时,TS 会根据数组的不同类型,来自动将类型变量设置为相应的类型
  • 技巧:可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键)来查看具体的类型信息

泛型工具类型

  • 泛型工具类型:TS 内置了一些常用的工具类型,来简化 TS 中的一些常见操作
  • 说明:它们都是基于泛型实现的(泛型适用于多种类型,更加通用),并且是内置的,可以直接在代码中使用。 这些工具类型有很多,主要学习以下几个:
  1. Partial<Type>
  2. Readonly<Type>
  3. Pick<Type, Keys>

1.Partial

  • Partial 用来构造(创建)一个类型,将 Type 的所有属性设置为可选。
type Props =  {
  id: string
  children: number[]
}
​
type PartialProps = Partial<Props>
复制代码
  • 解释:构造出来的新类型 PartialProps 结构和 Props 相同,但所有属性都变为可选的。

2.Readonly

  • Readonly 用来构造一个类型,将 Type 的所有属性都设置为 readonly(只读)。
type Props =  {
  id: string
  children: number[]
}
​
type ReadonlyProps = Readonly<Props>
复制代码
  • 解释:构造出来的新类型 ReadonlyProps 结构和 Props 相同,但所有属性都变为只读的。
let props: ReadonlyProps = { id: '1', children: [] }
// 错误演示
props.id = '2'
复制代码
  • 当我们想重新给 id 属性赋值时,就会报错:无法分配到 "id" ,因为它是只读属性。

3.Pick

  • Pick<Type, Keys> 从 Type 中选择一组属性来构造新类型。
interface Props {
  id: string
  title: string
  children: number[]
}
type PickProps = Pick<Props, 'id' | 'title'>
复制代码
  • 解释:

    1. Pick 工具类型有两个类型变量:1 表示选择谁的属性 2 表示选择哪几个属性。 2. 其中第二个类型变量,如果只选择一个则只传入该属性名即可。
    2. 第二个类型变量传入的属性只能是第一个类型变量中存在的属性。
    3. 构造出来的新类型 PickProps,只有 id 和 title 两个属性类型。