快速上手Typescript

TypeScript概念

本文不涉及类的概念,后续会补上,目前的内容足够帮助初学者快速了解和上手该语言了~

基本概念

TypeScript(TS)是JavaScript的超集

  • TypeScript = Type + JavaScript(在 JS 基础之上,添加了类型支持

  • TypeScript 是微软开发的开源编程语言,可以在任何运行 JavaScript 的地方运行

  • TypeScript 需要编译才能在浏览器运行

JavaScript 代码:

js 复制代码
// 没有明确的类型
let age = 18

TypeScript 代码:

ts 复制代码
// 有明确的类型,可以指定age是number类型(数值类型)
let age: number = 18

JavaScript 是解释型弱类型动态语言 ;TypeScript 是编译型强类型静态语言

JavaScript 的特点是灵活、高效、很短的代码就能实现复杂功能;缺点是没有代码提示,容易出错且编辑器不会给任何提示,而 TypeScript 则相反

解释型与编译型:

  • 解释型:代码无需编译直接运行,需要一个翻译器,一边翻译一边执行

  • 编译型:机器不能识别代码,需要一个编译器,将代码转换成机器能识别的语言(编译)

弱类型与强类型:

  • 弱类型:声明变量时无需指定类型

  • 强类型:声明变量时必须指定类型

动态语言与静态语言:

  • 动态语言:在代码执行的过程中可以动态添加对象的属性
  • 静态语言:不允许在执行过程中随意添加属性

安装与运行

安装

安装命令:

bash 复制代码
npm i -g typescript 
#or
yarn global add typescript

TypeScript 包:用来编译 TS 代码的包,提供了 tsc 命令,实现了 TS 到 JS 的转化

  • Mac 电脑安装全局包时,需要添加 sudo 获取权限: sudo npm i -g typescript

验证是否安装成功:tsc --v (查看 TypeScript 的版本)

编译运行

创建 .ts 文件 => 编译 TS => 执行 JS

  • 创建 hello.ts 文件(TS 文件的后缀名为 .ts )
  • 将 TS 编译为 JS:在终端中输入命令, tsc hello.ts (同级目录中会出现一个同名的 JS 文件)
  • 执行 JS 代码:在终端中输入命令, node hello.js

注意:所有合法的 JS 代码都是 TS 代码,真正在开发过程中,其实不需要自己手动的通过 tsc 把 ts 文件转成 js 文件,这些工作应该交给 webpack 或者 vite 来完成

创建基于TS的Vue项目

基于 vite 创建一个 vue 项目,使用 typescript 模板,命令如下:

bash 复制代码
# npm 6.x
npm create vite@latest my-vue-ts-app --template vue-ts

# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-ts-app -- --template vue-ts

# yarn
yarn create vite my-vue-ts-app --template vue-ts

# pnpm
pnpm create vite my-vue-ts-app --template vue-ts

学习语法阶段,可以直接使用官网编译器

TypeScript基础

类型概念

在 JavaScript 中类型定义了变量或表达式所能拥有的值集合和操作,可以将 TS 中的常用基础类型细分为两类:

JavaScript 已有类型:

  • 原始类型( number/string/boolean/null/undefined/symbol
  • 引用数据类型(数组,对象,函数等)

TypeScript 新增类型:

  • 联合类型
  • 自定义类型(类型别名type)、接口(interface)
  • 元组
  • 字面量类型、枚举
  • void、never、unknown

Typescript类型如下表所示:

类型 示例 描述
number 1、-33、2.5 数字
string 'hi'、"hi"、`hi` 字符串
boolean true、false 布尔值true或false
字面量 其本身 限制变量的值就是该字面量的值
any * 任意类型
unknown * 类型安全的any
void undefined、null null 或 undefined
never 无值 不能是任何值
object { name: 'jack' } 任意的JS对象
array [1,2,3] 任意JS数组
tuple [4,5] 元素,TS新增类型,固定长度数组
enum enum{ A, B } 枚举,TS中新增类型

类型关系图:

类型注解与类型推论

TypeScript 是 JS 的超集,TS 提供了 JS 的所有功能,并且额外的增加了: 类型系统 。为了显示告知 TypeScript 你使用了什么类型,需要使用注解 ,注解的形式为 value: type,就像是告诉类型检查器,这个 value 的类型为 type,以下为类型注解示例:

ts 复制代码
let age: number = 18 // age 是一个数字
let bool: boolean[] = [true, false] // bool 是一个布尔数组

TypeScript 类型系统的主要优势:可以显示标记出代码中的意外行为,从而降低了发生错误的可能性。此外,约定了什么类型,就只能给变量赋值该类型的值,否则就会报错

如果想让 TypeScript 推导类型,那就去掉注解,让 TypeScript 自动推导,这就是类型推论

ts 复制代码
let age = 18 // age 是一个数字
let bool = [true, false] // bool 是一个布尔数组

虽然 TypeScript 具有类型推论的功能,但对于初学者来说,建议加上类型注解

类型推论的两种常见场景:声明变量并初始化时、决定函数返回值时

常用类型

原始类型

  • 原始类型:number/string/boolean/null/undefined/symbol

  • 原始类型完全按照 JS 中类型的名称来书写

ts 复制代码
let age: number = 30;
let name: string = "John";
let isStudent: boolean = true;

// null类型只有null一个值
let person: null = null;
// undefined类型只有undefined一个值
let value: undefined = undefined;

let uniqueSymbol: symbol = Symbol("unique");
let bigIntValue: bigint = 100n;

any类型

any 是类型教父,为达目的不惜一切代价。any 类型的值就像常规的 JavaScript 一样,类型检查器完全发挥不了作用。除非万不得已,不要使用 any,在 TypeScript 中 any 主要是作为兜底类型使用,当你或者 TS 编译器无法确定类型时,默认就为 any 类型,其他情况应该尽量避免使用

ts 复制代码
// 变量设置类型为 any 后, 相当于对该变量关闭了类型检测
let num: any = 4;
num = 'hello';
//num 的类型时any,它是可以赋值给任意变量
let str: string;
str = num;// 这里会让s也失去类型检测(不安全)

// 声明变量如果不指定类型,则TS解析器会自动判断变量的类型为any(隐式的any)
let d; //any
d = 10;
d = 'hello';

如果确实需要使用 any,像下面这样使用:

ts 复制代码
let a: any = 666 //any
let b: any = ['danger'] //any
let c = a + b //any

正常情况下第三个语句将报错,但是却没有,因为我们告诉 TypeScript 相加的两个值都是 any 类型。如果想使用any,一定要显式注解,这相当于告诉 TypeScript,你知道自己在做什么

unknown类型

unknown 本质上就是类型安全的 any,如果无法预知一个值的类型,不要使用any,应该使用unknown

  • unknown 类型的值可以比较 (==、===、||、&&、?)、可以否定 (!)、可以使用 typeof 和 instanceof 运算符细化

用法如下:

ts 复制代码
let a: unknown = 30 //unknown
let b = a === 123 //boolean
let c = a + 10 //'a' is of type 'unknown'
if (typeof a === 'number') {
  let d = a + 10 //number
}

以上示例,我们大致可以了解 unknown 的用法:

  • TypeScript 不会把任何值推导为 unknown 类型,必须显式注解(变量a)
  • unknown 类型的值可以比较(变量b)
  • 执行操作时不能假定 unknown 类型的值为某种特定类型(变量c)
  • 必须先向 TypeScript 证明一个值确实是某个类型(变量d)
ts 复制代码
// unknown 类型类似于 any 类型,但更加类型安全,要求在使用之前进行类型检查或类型断言
let notSure: unknown = 4;
notSure = 'hello'; // 不能将类型"unknown"分配给类型"number"
let num: number = 4
num = notSure //此处会报错,unknow 比 any 更安全

void、never类型

除了 null 和 undefined 之外,TypeScript 还有 void 和 never 类型。这两个类型有明确的特殊作用,进一步划分不同情况下的不存在

  • void 是函数没有显式返回任何值时的返回类型(可以返回undefined
  • void 限制变量,只能将变量定义为undefined
ts 复制代码
// ⽆警告
function demo1(): void { }

// ⽆警告
function demo2(): void {
  return
}

// ⽆警告
function demo3(): void {
  return undefined
}

// 有警告:不能将类型"number"分配给类型"void"
function demo4(): void {
  return 666
}
  • never 是函数根本不返回(例如函数抛出异常,或者永远运行下去)时使用的类型
  • 几乎不用 never 限制变量,没有意义(限制变量就意味着该变量以后不能存任何的数据)
ts 复制代码
// never 类型表示永远不会有正常返回值的函数的返回类型,或者表示抛出异常的函数的返回类型。
function throwError(message: string): never {
    throw new Error(message);
}

数组类型

数组类型的两种写法:

ts 复制代码
// 写法一
let numbers: number[] = [1, 3, 5]
// 写法二
let strings: Array<string> = ['a', 'b', 'c']

更推荐使用 number[] 写法,更直观

元组类型

在 TypeScript 中,元组(Tuple )类型是一种特殊的数组类型(固定长度和特定类型顺序的数组)

  • 元组类型的语法是使用方括号 [] 并在其中指定各个元素的类型,多个类型之间使用逗号分隔,例如:
ts 复制代码
// 元组的长度和元素类型顺序需要与定义时相对应,否则会导致类型错误
let myTuple: [string, number, boolean] = ["Hello", 123, true];
  • 元组的使用方式与数组类似,可以通过索引来访问元素:
ts 复制代码
console.log(myTuple[0]); // 输出: "Hello"
console.log(myTuple[1]); // 输出: 123
console.log(myTuple[2]); // 输出: true
  • 元组还可以使用解构赋值来获取各个元素的值:
ts 复制代码
let [str, num, bool] = myTuple;
console.log(str); // 输出: "Hello"
console.log(num); // 输出: 123
console.log(bool); // 输出: true

Tuple在长度类型上是固定的,一旦定义后无法动态添加或删除元素,也无法改变元素的类型。如果需要更灵活的数组操作,可以使用普通的数组类型(Array)

字面量类型

任意的 JS 字面量(比如,对象、数字等)都可以作为类型使用。使用 js字面量 作为变量类型,这种类型就是字面量类型

JS 字面量主要有:{ name: 'jack' }[]20'abc'falsefunction() {}

  • 直接使用字面量类型
ts 复制代码
let a: '你好' // a的值只能为字符串"你好"
let b: 100 // b的值只能为数字100
a = '欢迎' // Type '"欢迎"' is not assignable to type '"你好"'
b = 200  // Type '200' is not assignable to type '100'
let gender: '男' | '⼥' //定义⼀个gender变量,值只能为字符串"男"或"⼥"
gender = '男'
gender = '未知' //不能将类型""未知""分配给类型""男" | "⼥""
  • 类型推断字面量类型
ts 复制代码
let str1 = 'Hello TS'
const str2 = 'Hello TS'

通过 TS 类型推论机制,可以得出:

  • 变量 str1 的类型为:string

  • 变量 str2 的类型为:'Hello TS'

str1 是一个变量(let),它的值可以是任意字符串,所以类型为string

str2 是一个常量(const),它的值不能变化只能是 'Hello TS',所以它的类型为'Hello TS',是一个字面量类型,也就是说某个特定的字符串也可以作为 TS 中的类型

字面量类型常常配合联合类型一起使用

  • 使用场景:用来表示一组明确的可选值列表

  • 如在贪吃蛇游戏中,游戏的方向的可选值只能是上、下、左、右中任意一个

ts 复制代码
// 使用自定义类型:
type Direction = 'up' | 'down' | 'left' | 'right'
function changeDirection(direction: Direction) {
  console.log(direction)
}
// 调用函数时,会有类型提示:
changeDirection('up')

参数 direction 的值只能是up/down/left/right中的任意一个,相比于string类型,使用字面量类型更加精确、严谨

  • keyof 与字面量类型
ts 复制代码
type Goods = {
  id: number
  name: string
}
// keyof 后接类型,返回字面量类型(键)
type Obj = keyof Goods
let obj: Obj = 'id'

枚举类型

基本使用

枚举的作用是列举类型中包含的各个值。这是一种无序数据结构,把键映射到值上。枚举可以理解为编译时键固定的对象,访问键时,TypeScript 将检查指定的键是否存在。枚举分为两种:字符串到字符串之间的映射和字符串到数字之间的映射

枚举注意事项:

  • 使用 enum 关键字定义枚举
  • 约定枚举名称以大写字母开头
  • 枚举中的多个值之间通过逗号分隔
  • 定义好枚举后,直接使用枚举名称作为类型注解
ts 复制代码
// 创建枚举
enum Direction { Up, Down, Left, Right }
// 使用枚举类型
function changeDirection(direction: Direction) {
  console.log(direction)
}
// 调用函数时,需要应该传入:枚举 Direction 成员的任意一个
// 类似于 JS 中的对象,直接通过 点(.)语法 访问枚举的成员
changeDirection(Direction.Up)

数字枚举

枚举成员是有值的,默认为从 0 开始自增的数值。枚举成员的值为数字的枚举,称为数字枚举

ts 复制代码
// 可以给枚举中的成员初始化值
enum Direction { Up = 10, Down, Left, Right } // Down -> 11、Left -> 12、Right -> 13
console.log(Direction)
/*{
  "10": "Up",
  "11": "Down",
  "12": "Left",
  "13": "Right",
  "Up": 10,
  "Down": 11,
  "Left": 12,
  "Right": 13
} */
enum Direction { Up = 2, Down = 4, Left = 8, Right = 16 }
console.log(Direction)
/*{
  "2": "Up",
  "4": "Down",
  "8": "Left",
  "16": "Right",
  "Up": 2,
  "Down": 4,
  "Left": 8,
  "Right": 16
}*/

字符串枚举

字符串枚举:枚举成员的值是字符串。字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值

ts 复制代码
enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT'
}
console.log(Direction)
/*{
  "Up": "UP",
  "Down": "DOWN",
  "Left": "LEFT",
  "Right": "RIGHT"
}*/

枚举实现原理

枚举是 TS 为数不多的非 JavaScript 类型级扩展(不仅仅是类型)的特性之一。其他类型仅仅被当做类型,而枚举不仅用作类型,还提供值(枚举成员都是有值的)。也就是说,其他的类型会在编译为 JS 代码时自动移除。但是,枚举类型会被编译为JS代码

ts 复制代码
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 = {})

一般情况下,推荐使用字面量类型 + 联合类型组合的方式,因为相比枚举,这种方式更加直观、简洁、高效

联合类型与交叉类型

联合类型

联合类型能将多个类型组合成一个类型,例如数组中既有 number 类型,也有 string 类型的写法:

ts 复制代码
let arr: (number | string)[] = [1, 'a', 3, 'b']

类型间使用 | 连接,代表类型可以是它们当中的其中一种,这种类型叫:联合类型

交叉类型

交叉类型(Intersection Types)允许将多个类型合并为一个类型。使用 & 符号连接多个类型,形成一个新的类型,这个新类型包含了所有类型的属性。交叉类型表示一个值同时具有多种类型的特性

ts 复制代码
type Person = {
  name: string;
  age: number;
};

type Employee = {
  employeeId: string;
  position: string;
};

type EmployeePerson = Person & Employee;

使用交叉类型时,被合并的类型的属性都会包含在新类型中。如果存在重复的属性名,它们的类型会合并成联合类型

类型别名

类型别名(自定义类型) :为任意类型起别名,可以更灵活的限制类型

  • 语法:type 类型别名 = 具体类型

  • 使用场景:当同一类型(复杂)被多次使用时,可以通过类型别名,简化该类型的使用

ts 复制代码
type CustomArray = (number | string)[]
let arr1: CustomArray = [1, 'a', 3, 'b']
let arr2: CustomArray = ['x', 'y', 6, 7]
  • 使用type关键字来创建自定义类型
  • 类型别名(比如,此处的 CustomArray )可以是任意合法的变量名称
  • 推荐使用大写字母开头(PascalCase命名法)
  • 创建类型别名后,直接使用该类型别名 作为变量的类型注解即可

函数

基本使用

类型在函数中的应用指的是: 函数参数返回值的类型

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

  • 单独指定参数、返回值的类型
ts 复制代码
// 函数声明
function add(num1: number, num2: number): number {
  return num1 + num2
}
// 箭头函数
const add = (num1: number, num2: number): number => {
  return num1 + num2
}
  • 同时指定参数、返回值的类型
ts 复制代码
// 使用类型别名
type AddFn = (num1: number, num2: number) => number

const add: AddFn = (num1, num2) => {
  return num1 + num2
}

函数中的void

  • 如果函数没有返回值,在 TS 的类型中,应该使用 void 类型
ts 复制代码
// 如果什么都不写,此时 add 函数的返回值类型为: void
const add = () => {}

// 明确指定函数返回值类型为 void
const add = (): void => {}

注意:

  • JS 中如果没有返回值,默认返回的是 undefined
  • voidundefinedTypeScript 中并不等同
  • 如果指定返回值类型是 undefined 那返回值必须是 undefined
typescript 复制代码
// 指定返回值类型为 undefined,函数体中必须显式 return undefined
const add = (): undefined => {
  return undefined
}

可选参数

使用?给函数指定可选参数类型,可选参数只能在参数列表的最后,可选参数后面不能再出现必选参数

ts 复制代码
function mySlice(start?: number, end?: number): void {
  console.log('起始索引:', start, '结束索引:', end)
}

对象

基本使用

JS 中的对象是由属性和方法构成的,而 TS 对象的类型就是在描述对象的结构(有什么类型的属性和方法)

  • 对象类型的写法:
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() {}
}
  • 使用{}来描述对象结构
  • 属性 采用属性名: 类型的形式
  • 方法 采用方法名(): 返回值类型的形式

箭头函数形式

  • 对象中方法的类型也可以使用箭头函数形式
ts 复制代码
type Person = {
  greet: (name: string) => void
  //等价于: greet(name: string): void
}

let person: Person = {
  greet(name) {
    console.log(name)
  }
}

对象可选属性

  • 对象的属性或方法,也可以是可选的,此时就用到可选属性
  • 可选属性的语法与函数可选参数的语法一致,都使用?来表示
ts 复制代码
type Config = {
  url: string
  method?: string
  fn?(): void
  fn1?: () => void
}

function myAxios(config: Config) {
  console.log(config)
}

使用类型别名

直接使用{}形式为对象添加类型,会降低代码的可读性,推荐使用类型别名为对象添加类型

ts 复制代码
// 创建类型别名
type Person = {
  name: string
  sayHi(): void
}

// 使用类型别名作为对象的类型
let person: Person = {
  name: 'jack',
  sayHi() {}
}

接口

基本使用

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

  • 使用interface关键字来声明接口 (和类型别名的意思一样)
  • 接口名称,可以是任意合法的变量名称,推荐以I开头
  • 接口的每一行只能有 一个 属性或方法,每一行不需要加分号
ts 复制代码
interface IPerson {
  name: string
  age: number
  sayHi(): void
}
let person: IPerson = {
  name: 'jack',
  age: 19,
  sayHi() {}
}

接口继承

  • 如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用
ts 复制代码
interface Point2D { x: number; y: number }
interface Point3D { x: number; y: number; z: number }

// 等价于
interface Point2D { x: number; y: number }
interface Point3D extends Point2D {
  z: number
}

使用 extends (继承)关键字实现了接口 Point3D 继承 Point2D

继承后,Point3D 就有了 Point2D 的所有属性和方法(此时,Point3D 同时有 x、y、z 三个属性)

type交叉类型

  • 使用 & 可以合并连接的对象类型,也叫:交叉类型
ts 复制代码
// 使用 type 来定义 Point2D 和 Point3D
type Point2D = {
  x: number;
  y: number;
};

// 使用 交叉类型 来实现接口继承的功能:
// 使用 交叉类型 后,Point3D === { x: number; y: number; z: number }
type Point3D = Point2D & {
  z: number;
};

let o: Point3D = {
  x: 1,
  y: 2,
  z: 3,
};

type与interface

interface(接口)和 type(类型别名)的对比:

  • 相同点:都可以给对象指定类型,在许多情况下,可以在它们之间自由选择
  • 不同点:
    • 接口只能为对象指定类型,并且接口可以继承
    • 类型别名可以为任意类型指定别名
ts 复制代码
interface IPerson {
  name: string
  age: number
  sayHi(): void
}
// 为对象类型创建类型别名
type IPerson = {
  name: string
  age: number
  sayHi(): void
}
// 为联合类型创建类型别名
type NumStr = number | string

除以上不同以外,type 和 interface 重复定义时也不同

  • type 不可重复定义
ts 复制代码
type Person = {
  name: string;
};
// 标识符 Person 重复  Error
type Person = {
  age: number;
};
  • interface 重复定义会合并
ts 复制代码
interface Person {
  name: string;
}
interface Person {
  age: number;
}
// 类型会合并,注意:属性类型和方法类型不能重复定义
const p: Person = {
  name: 'jack',
  age: 18,
};
interface type
支持:对象类型 支持:对象类型,其他类型
复用:可以继承(extends) 复用:交叉类(&)

类型断言

类型断言(Type Assertion)是一种在 TypeScript 中告诉编译器某个值的具体类型的方法。它允许开发者手动指定一个变量或表达式的类型,即使这个类型与编译器推断的类型不一致

在 TypeScript 中,类型断言有两种形式:

  • 尖括号语法:
ts 复制代码
let value: any = "Hello World";
let length: number = (<string>value).length;
  • as 语法:
ts 复制代码
let value: any = "Hello World";
let length: number = (value as string).length;

无论使用尖括号语法还是as语法,它们都可以将value的类型断言为string类型,以便在后续代码中使用string类型的方法和属性(如.length

类型断言仅在编译时起作用 ,不会进行真正的类型转换或运行时检查。它只是告诉编译器使用指定的类型来处理变量,一旦类型断言不正确,可能会导致运行时错误

类型断言常用于以下情况:

  • 开发者比编译器更了解某个变量的类型时,可以使用类型断言来覆盖编译器的类型推断
  • 在处理从动态类型语言(如JavaScript)转换为 TypeScript 的代码时,可以使用类型断言来逐步将动态类型转换为静态类型

过度使用类型断言可能会破坏类型安全性,应当慎重使用。尽量让编译器进行类型推断,只在必要的情况下使用类型断言

typeof 与 as 结合使用:

  • typeof 后面接值,返回类型
  • as 后接类型,用于给变量或者表达式手动指定类型
ts 复制代码
const obj = {
  name: 'jack',
  age: 18
}
let detail = {} as typeof obj

TypeScript泛型

基本概念

泛型参数使用尖括号<>声明。尖括号的位置限定泛型的作用域,只有少数几个地方可以使用尖括号。TypeScript 将确保当前作用域中相同的泛型参数最终都绑定同一个具体类型

  • 泛型是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口和类中

如:创建一个 id 函数,传入什么数据就返回该数据本身 (参数和返回值类型相同)

ts 复制代码
function id(value: number): number { return value }

// 使用泛型进行改造
function id<T>(value: T): T { return value }

改造后,调用时可以传入不同的参数,实现函数能够接受任意类型的效果:

ts 复制代码
id<number>(10) //number类型
id<string>('hello world') //string类型

调用时,可以省略<>,这样做,TypeScript会自动推论类型

泛型别名

泛型别名(Generic Type Aliases)允许你创建一个具有泛型参数的类型别名。这使得你可以在定义类型时使用泛型,以提高代码的灵活性和复用性

  • <T> 是一个变量,可以随意命名,建议遵循PascalCase命名法(大驼峰命名)
ts 复制代码
// 类型别名-使用泛型
type Data<T> = {
  id: number
  name: string
  data: T
}

// 原始类型
const obj1: Data<number> = {
  id: 1,
  name: 'jack',
  data: 1
}

// 字符串数组类型
const obj2: Data<string[]> = {
  id: 1,
  name: 'jack',
  data: ['1', '2']
}

// 字面量类型
type Name = 'jack' | 'lucy' | 'tom'
const obj3: Data<Name> = {
  id: 1,
  name: 'jack',
  data: 'lucy' // 只能为 jack、lucy、tom
}

// 函数类型
type Fn = (a: number, b: string) => void
const obj4: Data<Fn> = {
  id: 1,
  name: 'jack',
  data: (a: number, b: string) => {
    console.log(a, b)
  }
}

// 对象类型
type Obj = {
  id: number
  name: string 
  direction: 'up' | 'down' | 'left' | 'right'
}
const obj5: Data<Obj> = {
  id: 1,
  name: 'jack',
  data: {
    id: 1,
    name: 'jack',
    direction: 'down' // 只能为 up、down、left、right
  }
}

泛型函数

定义泛型函数

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

调用泛型函数

ts 复制代码
const num = id<number>(10)
const str = id<string>('a')
  • 语法:在函数名称的后面添加 <>尖括号中指定具体的类型,比如,此处的 number
  • 当传入类型 number 后,这个类型就会被函数声明时指定的类型变量 Type 捕获到。此时,Type 的类型就是 number。所以,函数 id 参数和返回值的类型都是 number
  • 如果传入类型 string,函数 id 参数和返回值的类型就都是 string。这样,通过泛型就做到了让 id 函数与多种不同的类型一起工作,实现了复用的同时保证了类型安全

简化泛型函数调用

ts 复制代码
// 省略 <number> 调用函数
let num = id(10)
let str = id('a')
  • 在调用泛型函数时,可以省略 <类型> 来简化泛型函数的调用
  • 此时,TS 内部会采用一种叫做类型参数推断的机制,来根据传入的实参自动推断出类型变量 Type 的类型。这种简化的方式调用泛型函数,使得代码更短,更易于阅读
  • 当编译器无法推断类型或者推断的类型不准确时,就需要显式地传入类型参数

泛型约束

泛型约束(Generic Constraints)是 TypeScript 中一种用于限制泛型类型参数的方法。通过泛型约束,你可以指定泛型类型参数必须满足某些条件,从而使代码更具安全性和可预测性

  • 比如,id('a')调用函数时获取参数的长度:
ts 复制代码
function id<Type>(value: Type): Type {
  console.log(value.length) //参数可能不存在 length 属性
  return value
}
id('a')

Type 可以代表任意类型,无法保证一定存在 length 属性。此时,就需要为泛型添加约束来收缩类型

添加泛型约束收缩类型,主要有两种方式:指定更具体类型添加约束

指定更具体类型

比如,将类型修改为Type[](Type 类型的数组)

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

添加约束

T extends Xxx:添加泛型约束,表示传入的类型必须满足接口的要求

ts 复制代码
// 定义接口
interface ILength { length: number } 

// Type extends ILength 添加泛型约束
function id<Type extends ILength>(value: Type): Type {
  console.log(value.length)
  return value
}
  • 创建描述约束的接口 ILength,该接口要求提供 length 属性
  • 通过 extends 关键字使用该接口,为泛型添加约束。该约束表示传入的类型必须具有length属性
  • 注意:传入的实参只要有 length 属性即可(类型兼容性)

多个类型变量

泛型的类型变量可以有多个,并且类型变量之间还可以约束

如第二个类型变量受第一个类型变量约束:

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

泛型接口

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

ts 复制代码
interface IdFunc<Type> {
  id: (value: Type) => Type
  ids: () => Type[]
}
let obj: IdFunc<number> = {
  id(value) { return value },
  ids() { return [1, 3, 5] }
}
  • 在接口名称的后面添加 <类型变量> ,那么,这个接口就变成了泛型接口
  • 接口的类型变量,对接口中所有其他成员可见,也就是接口中所有成员都可以使用类型变量
  • 使用泛型接口时,需要显式指定具体的类型

最常见的,JS 中的数组,就是一个泛型接口。当我们在使用数组时,TS 会根据数组的不同类型,来自动将类型变量设置为相应的类型

相关推荐
你选择放弃就不要再回头2 分钟前
Vue3动态时间显示
javascript·vue
努力的小玖心11 分钟前
Java将String类型的html文本中的img路径替换前缀
java·前端·html
菜鸟xy..12 分钟前
html 前端进行浮动布局设置
前端·html
赔罪16 分钟前
HTML - <link>
前端·vscode·html·webstorm
一只小爪子41 分钟前
通过 ulimit 和 sysctl 调整Linux系统性能
linux·运维·前端
YONG823_API43 分钟前
如何通过API实现淘宝商品评论数据抓取?item_review获取淘宝商品评论
大数据·开发语言·javascript·数据库·网络爬虫
一点一木1 小时前
前端报告 2024:全新数据,深度解析未来趋势
前端·javascript·vue.js
失眠的咕噜1 小时前
vue 导出excel接口请求和axios返回值blob类型处理
前端·javascript·vue.js
HelloZheQ1 小时前
CSS 伪类和伪元素:为你的选择器注入更多活力
前端·css
nt11071 小时前
一次性上传 1000 张图片, 总量 10GB 的方案设计
前端