Vue TS 入门指南

TypeScript 是什么

TypeScript(简称:TS)是 JavaScript 的超集(JS 有的 TS 都有)。

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

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

TypeScript 初体验

安装 ts

问题:为什么要安装编译 TS 的工具包?

回答:Node.js/浏览器,只认识 JS 代码,不认识 TS 代码。需要先将 TS 代码转化为 JS 代码,然后才能运行。

js 复制代码
pnpm i -g typescript

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

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

编译 ts 并运行

注意:TS 文件的后缀名为 .ts

编写 ts 文件,并使用命令将 ts 转换为 js,执行完毕,当前目录会出现一个 js文件

bash 复制代码
tsc .\hello.ts

简化运行 TS 的步骤 简化方式:使用 ts-node 包,直接在 Node.js 中执行 TS 代码。

安装命令:npm i -g ts-node(ts-node 包提供了 ts-node 命令)。

使用方式:ts-node hello.ts。

解释:ts-node 命令在内部偷偷的将 TS -> JS,然后,再运行 JS 代码。

常用类型

ts 复制代码
const age: number = 22

代码中的 : number 就是类型注解。

解释:约定了什么类型,就只能给变量赋值该类型的值,否则,就会报错。

类型概述

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

  1. JS 已有类型

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

    对象类型:object(包括,数组、对象、函数等对象)。

  2. TS 新增类型

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

原始类型

ts 复制代码
// 声明一个名为 age 的常量,类型为 number,赋值为 22
const age: number = 22;

// 声明一个名为 myName 的常量,类型为 string,赋值为空字符串
const myName: string = '';

// 声明一个名为 isLoading 的常量,类型为 boolean,赋值为 false
const isLoading: boolean = false;

// 声明一个名为 a 的常量,类型为 null,赋值为 null
const a: null = null;

// 声明一个名为 b 的常量,类型为 undefined,赋值为 undefined
const b: undefined = undefined;

// 声明一个名为 s 的常量,类型为 symbol,赋值为新创建的 Symbol
const s: symbol = Symbol();

数组类型

数组类型的两种写法:(推荐使用 number[] 写法)

ts 复制代码
const numbers: number[] = [2, 3, 4, 5, 3]
const strings: Array<string> = ['2', '3', '9']

联合类型

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

解释:| (竖线)在 TS 中叫做联合类型(由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种)。

ts 复制代码
// 声明一个名为 arr 的变量,类型为 string 或 number,赋值为数字 2
let arr: string | number = 2;

// 声明一个名为 arr2 的变量,类型为 number 或 string 数组,赋值为数字 2
let arr2: number | string[] = 2;

// 声明一个名为 arr3 的变量,类型为包含 number 或 string 元素的数组,赋值为包含数字 1 和字符串 '3' 的数组
let arr3: (number | string)[] = [1, '3'];

类型别名

类型别名(自定义类型):为任意类型起别名。

ts 复制代码
// type 定义一个类型为 person
type Person = {
    name: string;
    age: number;
    email?: string; // 可选属性
}
// 使用类型
let user: Person = {
    name: "Alice",
    age: 30,
    email: "alice@example.com"
}

函数类型

参数后面 + 返回值类型(: string),表示 String 类型

ts 复制代码
// 定义一个名为 add 的函数,接受两个参数 num1 和 num2,它们都是 number 类型
// 函数返回值类型为 string,将 num1 和 num2 相加后转换为字符串并返回
function add(num1: number, num2: number): string {
    return (num1 + num2).toString();
}

还有一种方式:同时指定参数、返回值的类型

ts 复制代码
/**
 * (num1: number, num2: number) 这块是参数类型
 * string 是返回值类型
 */
const add: (num1: number, num2: number) => string = (num1, num2) => {
    return (2 + 2).toString()
}

可选参数

可选参数:在可传可不传的参数名称后面添加 ?(问号)

注意:可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数

ts 复制代码
function greet(name: string, greeting?: string): void {
    if (greeting) {
        console.log(`${greeting}, ${name}!`)
    } else {
        console.log(`Hello, ${name}!`)
    }
}

greet('Alice') // 输出: Hello, Alice!
greet('Bob', 'Good morning') // 输出: Good morning, Bob!

对象类型

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

对象类型的写法:

语法:冒号 : {这里描述每个字段的类型,或者是函数的返回值,函数参数的类型}

ts 复制代码
let person: { name: string; age: number; sayHi(): void; greet(name: string): void } = {
    name: '刘老师',
    age: 18,
    sayHi() {
    },
    greet(name: string) {
    }
}

解释:

  1. 直接使用 {} 来描述对象结构。属性采用属性名: 类型的形式;方法采用方法名(): 返回值类型的形式。
  2. 如果方法有参数,就在方法名后面的小括号中指定参数类型(比如:greet(name: string): void)。
  3. 在一行代码中指定对象的多个属性类型时,使用 ;(分号)来分隔。
  4. 如果一行代码只指定一个属性类型(通过换行来分隔多个属性类型),可以去掉 ;(分号)。
  5. 方法的类型也可以使用箭头函数形式(比如:{ sayHi: () => void })

对象类型描述中,多行情况下,不需要加分号

对象类型可选参数

可选属性的语法与函数可选参数的语法一致,都使用 ?(问号)来表示。

ts 复制代码
// 定义一个 Person 类型,包含必选属性 name 和可选属性 age
type Person = {
    name: string;
    age?: number; // 可选属性 age
};

接口

ts 复制代码
// 声明一个接口
interface IPerson {
    name: string
    age: number

    sayHi(): void
}

// 实现该接口
let person: IPerson = {
    age: 0,
    name: '',
    sayHi(): void {
    }
}

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

相同点:都可以给对象指定类型。

不同点:

  • 接口,只能为对象指定类型。
  • 类型别名,不仅可以为对象指定类型,实际上可以为任意类型指定别名

接口继承

ts 复制代码
// 定义 Point2D 接口,表示二维坐标点
interface Point2D {
    x: number; // x 坐标属性
    y: number; // y 坐标属性
}

// 定义 Point3D 接口,扩展自 Point2D 接口,表示三维坐标点
interface Point3D extends Point2D {
    z: number; // z 坐标属性
}

// 声明一个变量 p3,类型为 Point3D,表示一个三维坐标点
let p3: Point3D = {
    x: 0, // 二维坐标 x
    y: 0, // 二维坐标 y
    z: 0  // 第三维坐标 z
}

元组

元组类型是另一种类型的数组,它确切地知道包含多少个元素,以及特定索引对应的类型。

解释:

  1. 元组类型可以确切地标记出有多少个元素,以及每个元素的类型
  2. 该示例中,元素有两个元素,每个元素的类型都是 number
ts 复制代码
const purple: [number, string] = [2, '2']

类型推论

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

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

发生类型推论的 2 种常见场景:1 声明变量并初始化时 2 决定函数返回值时。

ts 复制代码
// 这种情况会自动推论为 number
let age = 15

会自动根据返回值确定返回类型

ts 复制代码
function add(num1: number, num2: number) {
    return num1 + num2.toString()
}

类型断言

通过 byid 获取的a标签 元素,是一个父类的元素,并不是 a 标签,所以不具备 href 属性。所以需要 类型断言

as HTMLAnchorElement 为真实的元素

解释:

  1. 使用 as 关键字实现类型断言。
  2. 关键字 as 后面的类型是一个更加具体的类型(HTMLAnchorElement 是 HTMLElement 的子类型)。
  3. 通过类型断言,aLink 的类型变得更加具体,这样就可以访问 a 标签特有的属性或方法了
ts 复制代码
const aLink = document.getElementById('link') as HTMLAnchorElement

// 不常用
// const aLink = <HTMLAnchorElement>document.getElementById('link')

通过 console.dir($0) 打印当前对象信息,最下方有类型

字面量类型

通过 TS 类型推论机制,可以得到答案:

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

解释:

  1. str1 是一个变量(let),它的值可以是任意字符串,所以类型为:string。
  2. str2 是一个常量(const),它的值不能变化只能是 'Hello TS',所以,它的类型为:'Hello TS'。

此处的 'Hello TS',就是一个字面量类型。

使用模式:字面量类型配合联合类型一起使用。

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

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

枚举

枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值。

枚举:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个。

注意:形参 direction 的类型为枚举 Direction,那么,实参的值就应该是枚举 Direction 成员的任意一个, 直接通过点(.)语法访问枚举的成员。

ts 复制代码
// 定义一个枚举 Direction,表示方向
enum Direction {
    Up,    // 0
    Down,  // 1
    Left,  // 2
    Right  // 3
}

// 定义一个函数 changeDirection,接受一个 Direction 类型的参数
function changeDirection(direction: Direction) {
    // 在函数内部可以根据传入的 direction 参数做相应的处理
}

// 调用 changeDirection 函数,传入 Direction.Down 作为参数
changeDirection(Direction.Down);

解释:

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

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

具有初始值

ts 复制代码
// 纯数字枚举
enum NumericEnum {
    A = 1,
    B = 2,
    C = 3,
    D = 4
}

// 纯字符串枚举
enum StringEnum {
    Apple = "Apple",
    Banana = "Banana",
    Cherry = "Cherry",
    Date = "Date"
}

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

枚举类型会被编译为 JS 代码,枚举其实就是对象。

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

any 类型

原则:不推荐使用 any!这会让 TypeScript 变为 "AnyScript"(失去 TS 类型保护的优势)。

ts 复制代码
let variable: any;

variable = 123; // 可以赋值为 number 类型
variable = "hello"; // 可以赋值为 string 类型
variable = true; // 可以赋值为 boolean 类型
variable = [1, 2, 3]; // 可以赋值为数组类型
variable = { name: "Alice", age: 30 }; // 可以赋值为对象类型
variable = () => console.log("Hello"); // 可以赋值为函数类型

// 任意类型的值可以调用任意属性和方法,TypeScript 不会进行类型检查
console.log(variable.someUnknownProperty); // 不会报错,运行时可能出现 undefined 或其他错误
variable(); // 不会报错,运行时调用该函数

因为当值的类型为 any 时,可以对该值进行任意操作,并且不会有代码提示。

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

  1. 声明变量不提供类型也不提供默认值
  2. 函数参数不加类型。

注意:因为不推荐使用 any,所以,这两种情况下都应该提供类型!

typeof

TS 也提供了 typeof 操作符:可以在类型上下文中引用变量或属性的类型(类型查询)。

使用场景:根据已有变量的值,获取该值的类型,来简化类型书写。

ts 复制代码
// 定义变量 p,类型推断为具有 x 和 y 属性的对象,属性值类型为 number
let p = { x: 1, y: 2 };

// 定义函数 changeDirection,参数 direction 的类型为 typeof p
// 这意味着传入的 direction 参数必须与变量 p 的类型相匹配
function changeDirection(direction: typeof p) {
    // 函数体,这里可以根据传入的 direction 参数进行处理
}

// 调用 changeDirection 函数,传入一个具有相同结构的对象作为参数
changeDirection({ x: 1, y: 2 });

解释:

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

高级类型

class 类

TS 中的 class,不仅提供了 class 的语法功能,也作为一种类型存在。

constructor 是构造函数,只能有一个

ts 复制代码
class Person {
    age: number // 定义 age 属性,类型为 number
    gender: string // 定义 gender 属性,类型为 string

    // 构造函数,用于初始化 Person 实例的属性
    constructor() {
        // 构造函数中没有明确的初始化逻辑,属性会使用默认值
        // TypeScript 中类的属性默认值为 undefined
    }
}

// 创建一个 Person 类的实例
const person = new Person()
person.age

实例方法

在类中的方法,被定义为实例方法。

ts 复制代码
class Person {
    age: number // 定义 age 属性,类型为 number

    add() {
        this.age++
    }
}

继承、实现

类继承的两种方式:1 extends(继承父类) 2 implements(实现接口)。

说明:JS 中只有 extends,而 implements 是 TS 提供的。

ts 复制代码
class Dog extends Animal {

}

Person 类实现接口 Singable 意味着,Person 类中必须提供 Singable 接口中指定的所有方法和属性。

ts 复制代码
class Dog implements Singable {}

修饰符

可见性修饰符包括:1 public(公有的) 2 protected(受保护的) 3 private(私有的)。

只读修饰符

readonly:表示只读,用来防止在构造函数之外对属性进行赋值。

ts 复制代码
class Dog implements Singable {
    readonly age: number

    constructor(age: number) {
        this.age = age
    }
}

let dog = new Dog(22)

解释:

  1. 使用 readonly 关键字修饰该属性是只读的,注意只能修饰属性不能修饰方法。
  2. 接口或者 {} 表示的对象类型,也可以使用 readonly。

类型兼容性

类之间

我们先看一段代码,我们发现传递一个参数,ts 并没有给我们报错,我们点进去 forEach 方法看看。

ts 复制代码
arr.forEach(value => {})

我们发现,这是有三个参数的,但是为什么不报错呢,这就是类型兼容性。

ts 复制代码
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;

TS 采用的是结构化类型系统,也叫做 duck typing(鸭子类型),类型检查关注的是值所具有的形状。

也就是说,在结构类型系统中,对于对象类型来说,y 的成员至少与 x 相同,则 x 兼容 y(成员多的可以赋值给少的)。(类型、名称必须相同)

在下面的代码中,我们把 Dog 类型强转成 Dog2 类型,但是并没有报错,这就是类型兼容,ts 认为他们形状一样。

成员多的类可以赋值给成员少的类。

ts 复制代码
class Dog {
    x: number

    constructor(x: number) {
        this.x = x
    }
}

class Dog2 {
    x: number

    constructor(x: number) {
        this.x = x
    }
}

let dog: Dog2 = new Dog(2)

接口之间

接口之间的兼容性,类似于 class。并且,class 和 interface 之间也可以兼容。

函数之间的兼容性

函数之间兼容性比较复杂,需要考虑:1 参数个数 2 参数类型 3 返回值类型。

参数个数,参数多的兼容参数少的(或者说,参数少的可以赋值给多的),这点与类和接口是相反的

ts 复制代码
type F1 = (a: number) => void

type F2 = (a: number, b: number) => void

let f1: F1
let f2: F2

f2 = f1

解释:

  1. 参数少的可以赋值给参数多的,所以,f1 可以赋值给 f2。
  2. 数组 forEach 方法的第一个参数是回调函数,该示例中类型为:(value: string, index: number, array: string[]) => void。
  3. 在 JS 中省略用不到的函数参数实际上是很常见的,这样的使用方式,促成了 TS 中函数类型之间的兼容性。
  4. 并且因为回调函数是有类型的,所以,TS 会自动推导出参数 item、index、array 的类型。

参数类型,相同位置的参数类型要相同或兼容。

ts 复制代码
interface P3D {
    x: number
    y: number
}

interface P4D {
    x: number
    y: number
    z: number
}

type F3 = (p: P3D) => void
type F4 = (p: P4D) => void

let f3: F3
let f4: F4
// 参数少的复制给参数多的--可以
f4 = f3

解释:

  1. 注意,此处与前面讲到的接口兼容性冲突。
  2. 技巧:将对象拆开,把每个属性看做一个个参数,则,参数少的(f3)可以赋值给参数多的(f4)。

返回值类型,只关注返回值类型本身即可

接着上面的案例

ts 复制代码
type F3 = () => string
type F4 = () => string

let f3: F3
let f4: F4

f4 = f3
ts 复制代码
type F3 = () => {name: string}
type F4 = () => {name: string, age: number}

let f3: F3
let f4: F4

f4 = f3

解释:

  1. 如果返回值类型是原始类型,此时两个类型要相同,不一样,无法兼容
  2. 如果返回值类型是对象类型,此时成员多的可以赋值给成员少的(和类一样)

交叉类型

交叉类型(&):功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)。

ts 复制代码
// 定义 Person 接口,包含 name 属性
interface Person {
    name: string; // 姓名
}

// 定义 Contact 接口,包含 phone 属性
interface Contact {
    phone: string; // 电话号码
}

// 定义 PersonDetail 类型,它同时具有 Person 和 Contact 接口的属性
type PersonDetail = Person & Contact;

// 创建一个符合 PersonDetail 类型的对象
let obj: PersonDetail = {
    name: '', // 设置姓名为空字符串
    phone: '' // 设置电话号码为空字符串
}

解释:使用交叉类型后,新的类型 PersonDetail 就同时具备了 Person 和 Contact 的所有属性类型。

交叉类型(&)和接口继承(extends)的对比:

  1. 相同点:都可以实现对象类型的组合。
  2. 不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同。
ts 复制代码
interface Person {
    name: string
}

interface Contact extends Person{ // 此时这里会报错,提示 name 类型不兼容,因为父接口是 string,需要保持一致
    name: number
}

通过交叉类型,既可以用 string 也可以用 number

ts 复制代码
// 定义 Person 接口,包含一个接受 number 类型参数并返回 string 类型的函数 fn
interface Person {
    fn: (value: number) => string; // 接受 number 类型参数,返回 string 类型结果
}

// 定义 Contact 接口,包含一个接受 string 类型参数并返回 string 类型的函数 fn
interface Contact {
    fn: (value: string) => string; // 接受 string 类型参数,返回 string 类型结果
}

// 定义 PersonDetail 类型,该类型同时具有 Person 和 Contact 接口的 fn 属性
type PersonDetail = Person & Contact;

// 创建一个符合 PersonDetail 类型的对象 c
let c: PersonDetail = {
    // 实现 fn 属性,参数可以是 number 或 string 类型,返回值为 string 类型
    fn(value: number | string): string {
        return ''; // 返回空字符串
    }
}

泛型 和 keyof

泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活可复用。

ts 复制代码
function id<T>(value: T): T {
    return value
}

class Student {
    name: string
    age: number

    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
}

let student = new Student('佳佳', 21)
let id1 = id<Student>(student)
console.log(id1)

简化调用

ts 有类型推断机制,会根据传入的参数自动区分

ts 复制代码
// 显示传入类型参数
let id1 = id<Student>(student)
// 隐式传入
let id1 = id(student)

泛型约束

默认情况下,泛型函数的类型变量 Type 可以代表多个类型,这导致无法访问任何属性。

我们传入一个字符串,在泛型函数中调用length 属性,是无法调用的。

解释:Type 可以代表任意类型,无法保证一定存在 length 属性,比如 number 类型就没有 length。

此时,就需要为泛型添加约束来收缩类型(缩窄类型取值范围)。

那么我们如果想在泛型函数中,使用指定类型的数据。可以使用类型断言

ts 复制代码
function id<T>(value: T): T {
    let t = value as Student
    console.log(t.age)
    return value
}

添加约束

通过 extends 关键字使用该接口,为泛型(类型变量)添加约束。

注意:传入的实参(比如,数组)只要有 length 属性即可,这也符合前面讲到的接口的类型兼容性。

ts 复制代码
// 定义一个接口 ILength,表示具有 length 属性的对象
interface ILength {
    length: number; // 定义 length 属性为 number 类型
}

// 定义一个泛型函数 id,接受一个类型 T,要求 T 必须扩展自 ILength 接口
function id<T extends ILength>(value: T): T {
    console.log(value.length); // 打印参数 value 的 length 属性
    return value; // 返回参数 value,类型为 T
}

// 定义一个 Student 类
class Student {
    name: string;
    age: number;

    // 构造函数,接受 name 和 age 参数
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

let student = new Student('佳佳', 21);
// 由于 Student 类型不满足泛型约束(缺少 length 属性),所以会导致类型错误
let id1 = id(student); // 编译错误:Type 'Student' does not satisfy the constraint 'ILength'

keyof

keyof 关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型。

也就是说,keyof 会把对象类型中的key全部联合起来

ts 复制代码
// 定义一个泛型函数 id,接受两个参数:value 和 key
// value 的类型是 T,key 的类型是 T 的属性键
function id<T, K extends keyof T>(value: T, key: K) {
    return value[key]; // 返回 value 对象中 key 属性的值
}

// 定义一个对象 person,包含 name 和 age 属性
let person = {name: 'jack', age: 18};

// 调用 id 函数,传入 person 对象和 'age' 字符串作为 key
let id1 = id(person, 'age');

解释:

  1. 添加了第二个类型变量 Key,两个类型变量之间使用(,)逗号分隔。
  2. keyof 关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型。
  3. value 的类型是 T,key 的类型是 T 的属性键
  4. 类型变量 Key 受 Type 约束,可以理解为:Key 只能是 Type 所有键中的任意一个,或者说只能访问对象中存在的属性。

泛型接口

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

ts 复制代码
// 定义一个泛型接口 IdFunc<T>,表示具有 id 和 ids 方法的对象
interface IdFunc<T> {
    id: (value: T) => T; // id 方法接受一个类型为 T 的参数,并返回类型为 T 的值
    ids: () => []; // ids 方法不接受参数,返回一个空数组
}

// 创建一个符合 IdFunc<number> 类型的对象 obj
let obj: IdFunc<number> = {
    // 实现 id 方法,接受一个 number 类型的参数并返回 number 类型的值
    id(value: number): number {
        return 0; // 返回数字 0
    },
    // 实现 ids 方法,不接受参数,返回一个空数组
    ids(): [] {
        return []; // 返回空数组
    }
}

解释:

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

数组泛型接口

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

泛型类

由于构造方法传递参数了,所以 TS 可以推导出是什么类型,如果构造方法没有参数,需要指定类型。

类似于泛型接口,在创建 class 实例时,在类名后面通过 <类型> 来指定明确的类型。

ts 复制代码
// 定义一个泛型类 GenericNumber<T>
class GenericNumber<T> {
    value: T;

    constructor(value: T) {
        this.value = value;
    }
}

// 创建一个 GenericNumber 类的实例,传入 number 类型的值 200
let genericNumber = new GenericNumber(200);

// 下面的代码会导致编译错误
let genericNumber1 = new GenericNumber();

泛型工具类型

泛型工具类型:TS 内置了一些常用的工具类型,来简化 TS 中的一些常见操作。

说明:它们都是基于泛型实现的(泛型适用于多种类型,更加通用),并且是内置的,可以直接在代码中使用。

这些工具类型有很多,主要学习以下几个:

Partial

泛型工具类型 - Partial<Type> 用来构造(创建)一个类型,将 Type 的所有属性设置为可选。

ts 复制代码
// 定义一个接口 Props,表示具有 id 和 children 属性的对象
interface Props {
    id: string;
    children: number[];
}

// 使用 Partial<Props> 创建类型别名 PartialProps,表示 Props 类型属性都是可选属性
type PartialProps = Partial<Props>;

// 创建变量 a,类型为 PartialProps,即 Props 类型的部分可选属性
let a: PartialProps = {
    // 这里没有提供任何属性,a 是一个空对象,所有属性都是可选的
}

除此之外,还有 Readonly 表示只读

Pick

泛型工具类型 - Pick<Type, Keys> 从 Type 中选择一组属性来构造新类型。

ts 复制代码
// 定义一个接口 Props,表示具有 id、title 和 children 属性的对象
interface Props {
    id: string;
    title: string;
    children: number[];
}

// 使用 Pick<Props, 'id' | 'title'> 创建类型别名 PartialProps,表示从 Props 类型中挑选 'id' 和 'title' 属性
type PartialProps = Pick<Props, 'id' | 'title'>;

// 创建变量 obj,类型为 PartialProps,即 Props 类型的部分属性
let obj: PartialProps = {
    id: '', // 设置 id 属性为字符串类型的空值
    title: '' // 设置 title 属性为字符串类型的空值
}

解释:

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

Record

泛型工具类型 - Record<Keys,Type> 构造一个对象类型,属性键为 Keys,属性类型为 Type。

解释:

  1. Record 工具类型有两个类型变量:1 表示对象有哪些属性 2 表示对象属性的类型。
  2. 构建的新对象类型 RecordObj 表示:这个对象有三个属性分别为a/b/c,属性值的类型都是 string[]。
ts 复制代码
// 使用 Record<'id' | 'title', string[]> 创建类型别名 PartialProps
// 表示一个对象,具有 'id' 和 'title' 两个属性,且它们的值都是 string 类型的数组
type PartialProps = Record<'id' | 'title', string[]>;

// 创建变量 obj,类型为 PartialProps,即具有 'id' 和 'title' 属性的对象
let obj: PartialProps = {
    id: [], // 设置 id 属性为字符串数组类型的空数组
    title: [] // 设置 title 属性为字符串数组类型的空数组
}

索引签名

绝大多数情况下,我们都可以在使用对象前就确定对象的结构,并为对象添加准确的类型。

使用场景:当无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性),此时,就用到索引签名类型了。

ts 复制代码
// 定义一个接口 Props,表示具有任意字符串键和字符串值的对象
interface Props {
    [key: string]: string;
}

// 创建变量 obj,类型为 Props,即具有任意字符串键和字符串值的对象
let obj: Props = {
    id: '',     // 键为 'id',值为字符串类型的空字符串
    title: '',  // 键为 'title',值为字符串类型的空字符串
    aaa: ''     // 键为 'aaa',值为字符串类型的空字符串
}

解释:

  1. 使用 [key: string] 来约束该接口中允许出现的属性名称。表示只要是 string 类型的属性名称,都可以出现在对象中。
  2. 这样,对象 obj 中就可以出现任意多个属性(比如,a、b 等)。
  3. key 只是一个占位符,可以换成任意合法的变量名称。
  4. 隐藏的前置知识:JS 中对象({})的键是 string 类型的。

数组索引签名类型

在 JS 中数组是一类特殊的对象,特殊在数组的键(索引)是数值类型。

并且,数组也可以出现任意多个元素。所以,在数组对应的泛型接口中,也用到了索引签名类型。

解释:

  1. MyArray 接口模拟原生的数组接口,并使用 [n: number] 来作为索引签名类型。
  2. 该索引签名类型表示:只要是 number 类型的键(索引)都可以出现在数组中,或者说数组中可以有任意多个元素。
  3. 同时也符合数组索引是 number 类型这一前提。
ts 复制代码
// 定义一个泛型接口 Props<T>,表示具有数字索引和值类型为 T 的对象或数组
interface Props<T> {
    [n: number]: T; // 数字索引签名,表示可以用数字索引来访问元素,值的类型是 T
}

// 创建一个变量 arr,类型为 Props<number>,即数字索引对应的值类型为 number
let arr: Props<number> = [1, 3, 4, 5, 6, 7, 8];

映射类型

映射类型:基于旧类型创建新类型(对象类型),减少重复、提升开发效率。

根据联合类型

ts 复制代码
// 定义 PropKeys 类型为 'x' | 'y' | 'z' | 'k'
type PropKeys = 'x' | 'y' | 'z' | 'k';

// 定义 Type1 类型,其属性为 PropKeys 中的每个键对应一个 number 类型的值
type Type1 = { [key in PropKeys]: number };

// 创建一个符合 Type1 类型定义的对象
let obj: Type1 = {
    k: 0, // 属性 'k' 的值为 number 类型
    x: 0, y: 0, z: 0 // 属性 'x'、'y'、'z' 的值都为 number 类型
};

解释:

  1. 映射类型是基于索引签名类型的,所以,该语法类似于索引签名类型,也使用了 []。
  2. Key in PropKeys 表示 Key 可以是 PropKeys 联合类型中的任意一个,类似于 forin(let k in obj)。
  3. 使用映射类型创建的新对象类型 Type2 和类型 Type1 结构完全相同。
  4. 注意:映射类型只能在类型别名中使用,不能在接口中使用。

根据对象生成

ts 复制代码
type Props = { a: number, b: string, c: boolean };

// 定义 Type1 类型,其属性为 Props 类型中每个属性的键,并且值的类型都为 number
type Type1 = { [key in keyof Props]: number };

// 创建一个符合 Type1 类型定义的对象
let obj: Type1 = {
    a: 0, // 属性 'a' 的值为 number 类型
    b: 0, // 属性 'b' 的值为 number 类型
    c: 0  // 属性 'c' 的值为 number 类型
};

解释:

  1. 首先,先执行 keyof Props 获取到对象类型 Props 中所有键的联合类型即,'a' | 'b' | 'c'。
  2. 然后,Key in ... 就表示 Key 可以是 Props 中所有的键名称中的任意一个。

Partial 的实现

实际上,前面讲到的泛型工具类型(比如,Partial<Type>)都是基于映射类型实现的。

比如,Partial<Type> 的实现:

解释:

  1. keyof T 即 keyof Props 表示获取 Props 的所有键,也就是:'a' | 'b' | 'c'。
  2. 在 [] 后面添加 ?(问号),表示将这些属性变为可选的,以此来实现 Partial 的功能。
  3. 冒号后面的 T[P] 表示获取 T 中每个键对应的类型。比如,如果是 'a' 则类型是 number;如果是 'b' 则类型是 string。
  4. 最终,新类型 PartialProps 和旧类型 Props 结构完全相同,只是让所有类型都变为可选了。

索引查询(访问)类型

刚刚用到的 T[P] 语法,在 TS 中叫做索引查询(访问)类型。

作用:用来查询属性的类型。

ts 复制代码
type Props = { a: number, b: string, c: boolean };

// 定义 ta 类型为 Props 中属性 'a' 的类型,即 number
type ta = Props['a'];

// 创建一个变量 s,其类型为 ta,即 number
let s: ta = 2222; // 变量 s 被赋值为 number 类型的值 2222

解释:Props['a'] 表示查询类型 Props 中属性 'a' 对应的类型 number。所以,TypeA 的类型为 number。

注意:[] 中的属性必须存在于被查询类型中,否则就会报错。

同时多个

索引查询类型的其他使用方式:同时查询多个索引的类型

解释:使用字符串字面量的联合类型,获取属性 a 和 b 对应的类型,结果为: string | number。

解释:使用 keyof 操作符获取 Props 中所有键对应的类型,结果为: string | number | boolean

ts 复制代码
type Props = { a: number, b: string, c: boolean }
// string | number
type ta = Props['a' | 'b']
// string | number | boolean
type ta2 = Props[keyof Props]

TS 类型声明文件

类型声明文件:用来为已存在的 JS 库提供类型信息。

TS 的两种文件类型

TS 中有两种文件类型:1 .ts 文件 2 .d.ts 文件。

  • .ts 文件:

既包含类型信息又可执行代码。

可以被编译为 .js 文件,然后,执行代码。

用途:编写程序代码的地方。

  • .d.ts 文件:

只包含类型信息的类型声明文件。

不会生成 .js 文件,仅用于提供类型信息。

用途:为 JS 提供类型信息。

总结:.ts 是 implementation(代码实现文件);.d.ts 是 declaration(类型声明文件)。

如果要为 JS 库提供类型信息,要使用 .d.ts 文件。

类型声明文件的使用说明

在使用 TS 开发项目时,类型声明文件的使用包括以下两种方式:

  1. 使用已有的类型声明文件

  2. 创建自己的类型声明文件

使用已经有的类型声明文件

在 axios 中存在 index.d.ts 这个就是类型声明文件

它在哪里使用的呢?

在 package.json 中 typings 设置了类型声明文件的路径。

第二种方式 由 DefinitelyTyped 提供。

DefinitelyTyped 是一个 github 仓库,用来提供高质量 TypeScript 类型声明。

可以通过 npm/yarn 来下载该仓库提供的 TS 类型声明包,这些包的名称格式为:@types/*。

比如,@types/react、@types/lodash 等。

当安装 @types/* 类型声明包后,TS 也会自动加载该类声明包,以提供该库的类型声明。

补充:TS 官方文档提供了一个页面,可以来查询 @types/* 库。

查询地址 www.npmjs.com/~types

可以通过 npm/yarn 来下载该仓库提供的 TS 类型声明包,这些包的名称格式为:@types/*。

比如,@types/react、@types/lodash 等

创建自己的类型文件

创建自己的类型声明文件:1 项目内共享类型 2 为已有 JS 文件提供类型声明。

项目内共享类型:如果多个 .ts 文件中都用到同一个类型,此时可以创建 .d.ts 文件提供该类型,实现类型共享。

操作步骤:

  1. 创建 index.d.ts 类型声明文件。
  2. 创建需要共享的类型,并使用 export 导出(TS 中的类型也可以使用 import/export 实现模块化功能)。
  3. 在需要使用共享类型的 .ts 文件中,通过 import 导入即可(.d.ts 后缀导入时,直接省略)。
ts 复制代码
type Props = { a: number, b: string, c: boolean }

export {Props}

为已有 JS 文件提供类型声明:

  1. 在将 JS 项目迁移到 TS 项目时,为了让已有的 .js 文件有类型声明。
  2. 成为库作者,创建库给其他人使用。 注意:类型声明文件的编写与模块化方式相关,不同的模块化方式有不同的写法。但由于历史原因,JS 模块化的发展 经历过多种变化(AMD、CommonJS、UMD、ESModule 等),而 TS 支持各种模块化形式的类型声明。这就导致 ,类型声明文件相关内容又多又杂。 演示:基于最新的 ESModule(import/export)来为已有 .js 文件,创建类型声明文件。 开发环境准备:使用 webpack 搭建,通过 ts-loader 处理 .ts 文件。

说明:TS 项目中也可以使用 .js 文件。

说明:在导入 .js 文件时,TS 会自动加载与 .js 同名的 .d.ts 文件,以提供类型声明。 declare 关键字:用于类型声明,为其他地方(比如,.js 文件)已存在的变量声明类型,而不是创建一个新的变量。

  1. 对于 type、interface 等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略 declare 关键字。
  2. 对于 let、function 等具有双重含义(在 JS、TS 中都能用),应该使用 declare 关键字,明确指定此处用于类型声明。
相关推荐
用户33790448021718 分钟前
HTML5语义化标签详解
前端
唐某人丶21 分钟前
教你如何用 JS 实现一个 Agent 系统(1)—— 认识 Agentic System
前端·人工智能
丘山子26 分钟前
分享链接格式不统一,rel="share-url" 提案试图解决这个问题
前端·面试·html
JustHappy1 小时前
「Versakit攻略」🔥Pnpm+Monorepo+Changesets搭建通用组件库项目和发包流程
前端·javascript·vue.js
紫金龙腾2 小时前
EDGE 、chrome、浏览器显示“由你的组织管理”
前端·chrome·edge
用户66197734585752 小时前
Vue3笔记
前端·vue.js
2401_837088503 小时前
ref 简单讲解
前端·javascript·vue.js
折果4 小时前
如何在vue项目中封装自己的全局message组件?一步教会你!
前端·面试
不死鸟.亚历山大.狼崽子4 小时前
Syntax Error: Error: PostCSS received undefined instead of CSS string
前端·css·postcss
汪子熙4 小时前
Vite 极速时代的构建范式
前端·javascript