深入学习TS

290 阅读8分钟

今天参加字节面试,字节小哥很和蔼,说看看我ts功底怎么样,结果我被问得亚麻呆住了,然后面完认真学习了一波,认真复习了一波

参考资料:www.patrickzhong.com/TypeScript/…

一、基础类型

TypeScript 的基础类型包括布尔值、数字、字符串、数组、元组、枚举、任意值、空值、Null、Undefined、Never、以及类型断言。

类型描述用法和示例
布尔值表示逻辑值:truefalselet isDone: boolean = false;
数字表示数值(包括整数和浮点数)。let decimal: number = 6;
字符串表示文本或字符序列。let color: string = "blue";
数组表示相同类型元素的集合。let numbers: number[] = [1, 2, 3];
元组表示具有特定类型的固定长度数组。let tuple: [string, number] = ["hello", 42];
枚举为一组数值赋予更友好的名称。enum Color { Red, Green, Blue };
let bgColor: Color = Color.Blue;
任意值允许变量保存任何类型的值。let variable: any = "any type";
variable = 42;
variable = true;
空值表示没有类型。通常用于不返回值的函数。function logMessage(): void { console.log("Hello!"); }
let result: void = logMessage();
Null表示空值或根本没有值。let nullValue: null = null;
Undefined表示未定义的值,通常是未初始化变量的默认值。let undefinedValue: undefined = undefined;
let x: number; // x is undefined
Never表示永远不会发生的值。通常用于总是抛出异常或永不返回的函数。function throwError(message: string): never { throw new Error(message); }
function infiniteLoop(): never { while (true) { } }
Unknown表示未知类型的值。与 any 类型类似,但对 unknown 类型的值进行操作会更加安全。let userInput: unknown;
let userName: string = userInput as string;
let userLength: number = (userInput as string).length; 或使用类型断言 <Type>

anyunknown 是 TypeScript 中两种用于处理不明确类型的方式,但它们在使用和类型安全方面有一些重要的区别。

面试一问

1、unknown与any区别

any

  1. 任意类型赋值: any 类型可以接受任何类型的赋值,且对它的操作不进行类型检查。

    let value: any = 42;
    value = 'hello';
    value = [1, 2, 3];
    
  2. 类型检查绕过: 在使用 any 类型时,TypeScript 不会对变量的类型进行检查,因此可能导致潜在的运行时错误。

    let value: any = 42;
    console.log(value.toFixed(2)); // 编译通过,但在运行时可能出错
    

unknown

  1. 安全赋值: unknown 类型也可以接受任何类型的赋值,但对它的操作会进行类型检查。相比于 anyunknown 的使用更加安全。

    let value: unknown = 42;
    value = 'hello';
    value = [1, 2, 3];
    
  2. 类型检查强制: 在使用 unknown 类型时,必须在进行具体类型的操作之前进行类型检查或类型断言。

    let value: unknown = 42;
    // value.toFixed(2); // 错误:Object is of type 'unknown'
    
    if (typeof value === 'number') {
        console.log(value.toFixed(2)); // 正确:类型检查通过
    }
    
  3. 更安全的类型推导: 在使用 unknown 类型时,TypeScript 会推导出更精确的类型,有助于在编译时捕获潜在错误。

    let value: unknown = 42;
    let stringValue: string = value; // 错误:不能将类型 'unknown' 分配给类型 'string'
    
    if (typeof value === 'string') {
        let anotherStringValue: string = value; // 正确:类型检查通过
    }
    

总体而言,unknown 提供了更严格的类型检查,更有助于类型安全。在可能的情况下,应优先使用 unknown,而不是 any,以提高代码的可维护性和安全性。

2、ts 类型中 null 和undefined区别在哪里呀

在 TypeScript 中,nullundefined 都表示某个值的缺失或不存在,但它们在类型系统中有一些区别。

1. nullundefined 的默认类型

  • 默认情况下,nullundefined 是所有类型的有效值。这意味着它们可以赋值给任何类型的变量。
let a: number = null;      // 编译通过
let b: string = undefined; // 编译通过

2. --strictNullChecks 开启时的行为

  • 当 TypeScript 的 --strictNullChecks 选项开启时,nullundefined 不再是所有类型的有效值。此时,要将它们赋值给其他类型的变量,需要使用联合类型或明确地将变量声明为 nullundefined
let c: number = null;      // 错误,不能将类型 'null' 分配给类型 'number'
let d: string = undefined; // 错误,不能将类型 'undefined' 分配给类型 'string'

3. undefined 类型

  • undefined 类型表示一个值未被初始化。当一个变量被声明但未赋值时,默认为 undefined
let x: undefined;
console.log(x); // 输出: undefined

4. null 类型

  • null 类型表示一个值被明确地设置为 null
let y: null = null;
console.log(y); // 输出: null

5. undefined 和 null 是所有类型的子类型

  • undefinednull 是所有类型的子类型,这意味着你可以将它们赋值给任何类型的变量。
let z: number = null;
let w: string = undefined;

虽然 nullundefined 在某些情况下可以互换使用,但它们在类型系统中有一些微妙的区别。在实践中,推荐使用 --strictNullChecks 选项,并尽可能使用联合类型或显式地使用 nullundefined 类型,以提高代码的可读性和类型安全性。

二、接口

TypeScript 的核心原则之一是对值所具有的_结构_进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在 TypeScript 里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

接口(Interfaces)是 TypeScript 中用于定义对象的结构和类型的一种抽象方式。它可以包含属性、方法、索引签名等,用于描述类似结构的数据。以下是对接口中的知识点的详细介绍:

基本用法

  1. 属性:

    • 使用 interface 关键字定义接口,通过属性描述对象的结构。
    interface Person {
        name: string;
        age: number;
    }
    
  2. 可选属性:

    • 使用 ? 表示属性是可选的。
    interface Person {
        name: string;
        age?: number;
    }
    
  3. 只读属性:

    • 使用 readonly 关键字表示属性只能在对象创建时被赋值。
    interface Point {
        readonly x: number;
        readonly y: number;
    }
    
  4. 函数类型:

    • 定义一个接口,描述一个函数的参数和返回值类型。
    interface AddFunction {
        (x: number, y: number): number;
    }
    

继承与实现

  1. 继承接口:

    • 使用 extends 关键字实现接口继承。
    interface Shape {
        color: string;
    }
    
    interface Square extends Shape {
        sideLength: number;
    }
    
  2. 接口继承类:

    • 接口可以继承类,接口会继承类的成员但不包含其实现。
    class Animal {
        move() {
            console.log("Moving...");
        }
    }
    
    interface Dog extends Animal {
        bark(): void;
    }
    

混合类型与接口的交互

  1. 混合类型:

    • 一个接口可以描述具有多种类型的对象。
    interface Counter {
        (): number;
        reset(): void;
    }
    
    function createCounter(): Counter {
        let count = 0;
        const counter: Counter = () => {
            count++;
            return count;
        };
        counter.reset = () => {
            count = 0;
        };
        return counter;
    }
    

索引签名

  1. 索引签名:

    • 使用索引签名允许对象中包含任意属性。
    interface Dictionary {
        [key: string]: string;
    }
    
    const colors: Dictionary = {
        red: "FF0000",
        green: "00FF00",
        blue: "0000FF"
    };
    

接口和类的关系

  1. 类实现接口:

    • 类可以通过 implements 关键字实现接口。
    interface Clock {
        currentTime: Date;
        setTime(d: Date): void;
    }
    
    class WallClock implements Clock {
        currentTime: Date = new Date();
    
        setTime(d: Date): void {
            this.currentTime = d;
        }
    }
    
  2. 接口继承类:

    • 接口还可以继承类,会继承类的成员但不包含其实现。
    class Control {
        private state: any;
    }
    
    interface SelectableControl extends Control {
        select(): void;
    }
    
    class Button extends Control implements SelectableControl {
        select(): void {
            // Implementation...
        }
    }
    

这些是 TypeScript 中接口的一些基本用法和高级特性。接口提供了一种结构化的方式来定义对象的形状,使代码更加清晰、可读,同时也提供了一些面向对象编程的特性,如继承和多态。

三、函数

函数是JavaScript应用程序的基础。 它帮助你实现抽象层,模拟类,信息隐藏和模块。 在TypeScript里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义_行为_的地方。 TypeScript为JavaScript函数添加了额外的功能,让我们可以更容易地使用。

函数声明

  1. 基本形式:

    function add(x: number, y: number): number {
        return x + y;
    }
    

    这个例子中,函数 add 接受两个参数 xy,它们都是 number 类型,返回值也是 number 类型。

  2. 可选参数:

    function greet(name: string, greeting?: string): string {
        if (greeting) {
            return `${greeting}, ${name}!`;
        } else {
            return `Hello, ${name}!`;
        }
    }
    

    参数 greeting 带有问号,表示它是可选的。在调用时可以省略。

  3. 默认参数:

    function multiply(x: number, y: number = 2): number {
        return x * y;
    }
    

    参数 y 有一个默认值,如果调用时未提供该参数,将使用默认值。

函数表达式

  1. 匿名函数:

    let add = function(x: number, y: number): number {
        return x + y;
    };
    
  2. 箭头函数:

    let multiply = (x: number, y: number): number => x * y;
    

    箭头函数的语法更为简洁,而且它的 this 指向是在定义时确定的。

可选参数和剩余参数

  1. 可选参数和剩余参数:

    function displayInfo(name: string, age?: number, ...hobbies: string[]): void {
        console.log(`Name: ${name}`);
        if (age) {
            console.log(`Age: ${age}`);
        }
        if (hobbies.length > 0) {
            console.log(`Hobbies: ${hobbies.join(", ")}`);
        }
    }
    

    这里使用了可选参数 age 和剩余参数 hobbies

函数重载

  1. 函数重载:

    function greet(person: string): string;
    function greet(person: string, greeting: string): string;
    
    function greet(person: string, greeting?: string): string {
        if (greeting) {
            return `${greeting}, ${person}!`;
        } else {
            return `Hello, ${person}!`;
        }
    }
    

    使用函数重载可以根据参数的不同数量或类型返回不同的结果。

函数类型

  1. 函数类型:

    • type 定义

      type MathFunction = (x: number, y: number) => number;
      
      let add: MathFunction = function(x, y) {
          return x + y;
      };
      
    • 接口定义

      interface MathFunction {
          (x: number, y: number): number;
      }
      

    可以使用类型别名定义函数类型,提高代码的可读性和复用性。

四、字面量类型

一个字面量是一个集体类型中更为具体的一种子类型。意思是:"Hello World" 是一个 string,但是一个 string 不是类型系统中的 "Hello World"

目前 TypeScript 中有三种可用的字面量类型集合,分别是:字符串、数字和布尔值。通过使用字面量类型,你可以规定一个字符串、数字或布尔值必须含有的确定值。

字符串字面量类型

let status: "success" | "error";
status = "success"; // 合法
status = "pending"; // 错误,只能是 "success" 或 "error"

上面的例子中,status 只能是字符串字面量 "success" 或 "error",其他字符串值是不允许的。

数字字面量类型

let score: 1 | 2 | 3;
score = 1; // 合法
score = 4; // 错误,只能是 1、2 或 3

在这个例子中score 只能是数字字面量 1、2 或 3,其他数字是不允许的。

布尔字面量类型

let isTrue: true | false;
isTrue = true; // 合法
isTrue = false; // 合法
isTrue = 1; // 错误,只能是 true 或 false

isTrue 只能是布尔字面量 true 或 false。

结合字面量类型的联合类型

type Color = "red" | "green" | "blue";

function paint(color: Color) {
    // ...
}

paint("red"); // 合法
paint("yellow"); // 错误,只能是 "red"、"green" 或 "blue"

在这个例子中,Color 是一个字符串字面量类型的联合类型,表示颜色只能是 "red"、"green" 或 "blue"。

字面量类型通常与联合类型、类型别名以及其他高级类型结合使用,以创建更精确和具体的类型定义。这种方式可以提高代码的可读性和类型安全性。

字面量收窄

字面量收窄(Literal Narrowing)指的是在 TypeScript 中,当使用字面量类型(例如字符串、数字、布尔字面量类型)进行赋值或比较操作时,编译器会推导出更具体的类型,从而缩小变量的类型范围。

这种收窄是 TypeScript 类型系统的一部分,它基于代码分析和类型推导,根据上下文信息缩小变量的类型范围。字面量收窄通常与联合类型一起使用,以提供更具体的类型信息。

五、联合类型和交叉类型

交叉类型和联合类型是组合类型的方式之一。

联合类型

联合类型表示一个值可以属于多个类型之一。使用 | 符号将多个类型列在一起。

// 联合类型
let variable: string | number;

variable = "hello"; // 合法
variable = 42; // 合法
variable = true; // 错误,只能是 string 或 number

在上述例子中,variable 可以是 stringnumber 类型。在使用联合类型的值时,TypeScript 会根据上下文进行类型推导,使得代码在使用这个值的时候可以充分利用它可能具有的多种类型的特性。

function displayType(input: string | number): void {
    if (typeof input === "string") {
        console.log("It's a string");
        console.log(input.length); // 合法,TypeScript 在这个分支中收窄类型为 string
    } else {
        console.log("It's a number");
        console.log(input.toFixed(2)); // 合法,TypeScript 在这个分支中收窄类型为 number
    }
}

交叉类型

交叉类型表示一个值同时具备多个类型的特性。使用 & 符号将多个类型组合在一起。

// 交叉类型
interface Shape {
    color: string;
}

interface Area {
    area: number;
}

type Circle = Shape & Area;

let myCircle: Circle = {
    color: "red",
    area: 25
};

在上述例子中,Circle 类型表示同时具有 ShapeArea 两个类型的特性。这种类型通常在对象合并时使用,让一个对象具备多个类型的属性。

function combineObjects(obj1: Shape, obj2: Area): Shape & Area {
    return { ...obj1, ...obj2 };
}

let combined: Circle = combineObjects({ color: "blue" }, { area: 30 });

combined 对象同时具备了 ShapeArea 的属性。

综上所述,联合类型和交叉类型是 TypeScript 中用于组合多个类型的强大工具,它们分别强调一个值可以是多种类型之一,或者同时具备多个类型的特性。

六、类

TypeScript 中的类(Class)基本上遵循 ECMAScript 2015(ES6)中引入的类的概念,但添加了一些额外的静态类型特性。

1. 类的基本定义

使用 class 关键字定义一个类,可以包含构造函数、属性、方法等。

class Person {
    // 构造函数
    constructor(public name: string, public age: number) {
    }

    // 方法
    sayHello(): string {
        return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
    }
}

// 创建类的实例
let person = new Person("Alice", 30);
console.log(person.sayHello());

2. 继承

通过 extends 关键字可以实现类的继承。

class Student extends Person {
    constructor(name: string, age: number, public studentId: string) {
        super(name, age); // 调用父类构造函数
    }

    // 重写父类方法
    sayHello(): string {
        return `Hello, I am a student. ${super.sayHello()}`;
    }
}

let student = new Student("Bob", 25, "12345");
console.log(student.sayHello());

3. 访问修饰符

在类的成员前可以使用访问修饰符(publicprivateprotected)来限制成员的访问。

class Animal {
    private name: string;

    constructor(name: string) {
        this.name = name;
    }

    move(): string {
        return `${this.name} is moving.`;
    }
}

let animal = new Animal("Dog");
console.log(animal.move()); // 合法
console.log(animal.name); // 错误,私有成员不可访问

4. 抽象类和抽象方法

通过 abstract 关键字可以定义抽象类和抽象方法,抽象类不能被实例化,只能被继承。

abstract class Shape {
    abstract getArea(): number;
}

class Circle extends Shape {
    constructor(private radius: number) {
        super();
    }

    getArea(): number {
        return Math.PI * this.radius ** 2;
    }
}

let circle = new Circle(5);
console.log(circle.getArea());

5. 静态成员

使用 static 关键字可以定义静态成员,静态成员属于类而不是实例。

class MathUtils {
    static PI: number = 3.14;

    static add(x: number, y: number): number {
        return x + y;
    }
}

console.log(MathUtils.PI);
console.log(MathUtils.add(2, 3));

6. 实现接口

类可以实现接口,通过 implements 关键字实现接口的要求。

interface Printable {
    print(): void;
}

class Document implements Printable {
    print(): void {
        console.log("Printing document...");
    }
}

这些是 TypeScript 中类的基本用法,类也支持更多的特性,如属性存取器、参数属性、类表达式等。类是面向对象编程的重要概念,在 TypeScript 中提供了强大的类型支持和静态类型检查。

七、枚举

在 TypeScript 中,枚举(Enum)是一种用于命名一组有名字的数字常数的数据类型。枚举使用 enum 关键字定义,它可以包含数字和字符串成员,并且可以具有初始值。

1. 数字枚举

enum Direction {
    Up,
    Down,
    Left,
    Right,
}

let move: Direction = Direction.Up;
console.log(move); // 输出: 0

// 可以通过值或名称进行访问
console.log(Direction[1]); // 输出: Down

在数字枚举中,成员的值默认从 0 开始自增。你也可以手动赋值:

enum Direction {
    Up = 1,
    Down,
    Left,
    Right,
}

2. 字符串枚举

字符串枚举成员具有明确的字符串值。

enum Color {
    Red = "RED",
    Green = "GREEN",
    Blue = "BLUE",
}

let selectedColor: Color = Color.Green;
console.log(selectedColor); // 输出: GREEN

3. 异构枚举

异构枚举可以同时包含字符串和数字成员。

enum LogLevel {
    Info = 1,
    Warn = "WARN",
    Error = 3,
}

let logLevel: LogLevel = LogLevel.Warn;
console.log(logLevel); // 输出: WARN

4. 常量枚举

常量枚举在编译阶段会被删除,只保留枚举成员的值。

const enum Status {
    Success = 200,
    NotFound = 404,
    Error = 500,
}

let statusCode: Status = Status.Success;
console.log(statusCode); // 输出: 200

5. 枚举成员和枚举值

枚举成员(Enum Member)指的是枚举类型中的每个具体的值,而枚举值(Enum Value)则是该成员的实际值。例如,在 Direction.Up 中,Up 是枚举成员,而 0 是枚举值。

6. 反向映射

数字枚举具有反向映射,可以通过枚举值获取对应的枚举成员名称。

enum Direction {
    Up,
    Down,
    Left,
    Right,
}

let directionName: string = Direction[0]; // 获取 Up

7. 枚举类型的应用

枚举类型常用于表示一组相关的常量,例如方向、状态、颜色等。在代码中使用枚举可以提高代码的可读性和可维护性。

function moveInDirection(direction: Direction): void {
    // 根据方向进行移动
}

moveInDirection(Direction.Up);

这样,通过 TypeScript 枚举,可以更加清晰地表达和使用一组有限的常量。

八、泛型

泛型(Generics)是 TypeScript 中一种强大的特性,它允许在函数、类、接口等中使用参数化类型,从而实现更灵活和可重用的代码。泛型的基本思想是使用占位符(类型参数)来表示类型,在实际使用时再填充具体的类型。

1. 泛型函数

function identity<T>(arg: T): T {
    return arg;
}

let result: number = identity<number>(42);
let result2: string = identity<string>("hello");

在上面的例子中,<T> 表示类型参数,可以理解为一个占位符。调用 identity 函数时,通过 <number><string> 指定具体的类型。

2. 泛型数组和元组

function reverse<T>(items: T[]): T[] {
    return items.reverse();
}

let numbers: number[] = [1, 2, 3, 4, 5];
let reversedNumbers: number[] = reverse(numbers);

let strings: string[] = ["apple", "banana", "orange"];
let reversedStrings: string[] = reverse(strings);

泛型可以应用于数组,以实现对不同类型数组的逆序操作。

3. 泛型接口

interface Pair<T, U> {
    first: T;
    second: U;
}

let pair1: Pair<number, string> = { first: 1, second: "two" };
let pair2: Pair<string, boolean> = { first: "hello", second: true };

泛型接口允许在接口中使用泛型类型参数,实现更灵活的接口定义。

4. 泛型类

class Box<T> {
    private value: T;

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

    getValue(): T {
        return this.value;
    }
}

let numberBox = new Box<number>(42);
let stringBox = new Box<string>("hello");

console.log(numberBox.getValue()); // 输出: 42
console.log(stringBox.getValue()); // 输出: hello

泛型类允许在类中使用泛型类型参数,从而创建具有泛型特性的类。

5. 泛型约束

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length); // 通过泛型约束保证 arg 具有 length 属性
    return arg;
}

loggingIdentity("hello"); // 合法
loggingIdentity(42); // 错误,number 类型没有 length 属性

泛型约束允许限制泛型类型参数必须符合某些条件,使泛型类型更具约束性。

6. 泛型默认值

function defaultValue<T = string>(arg: T): T {
    return arg;
}

let result1: number = defaultValue(42); // T 推断为 number
let result2: string = defaultValue("hello"); // T 推断为 string

在定义泛型函数或类时,可以为泛型类型参数提供默认值,使在不显式指定类型参数时,它会被默认为指定的类型。

泛型是 TypeScript 中强大而灵活的工具,它使得我们可以编写更通用、可维护的代码,同时保持类型安全。

九、高级类型

1. 交叉类型

交叉类型用于将多个类型合并成一个类型。例如,将两个接口合并成一个新接口:

interface Person {
    name: string;
}

interface Address {
    address: string;
}

type PersonWithAddress = Person & Address;

let person: PersonWithAddress = {
    name: "Alice",
    address: "123 Main St"
};

在上述例子中,PersonWithAddress 包含了 PersonAddress 接口的所有成员。

2. 联合类型

联合类型用于表示一个值可以是多种类型之一。例如,一个变量可以是 stringnumber

type Result = string | number;

let value: Result = "success";
value = 42;

3. 类型别名

类型别名允许你为类型创建别名,提高代码的可读性和可维护性:

type Point = { x: number; y: number };

function distance(p1: Point, p2: Point): number {
    return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
}

4. 映射类型

映射类型允许你根据现有类型创建新类型,通过在现有类型的每个属性上应用一个函数来实现:

type Flags = {
    option1: boolean;
    option2: boolean;
};

type OptionalFlags = {
    [K in keyof Flags]?: boolean;
};

let optionalFlags: OptionalFlags = {};

5. 条件类型

条件类型根据条件选择其中的一种类型,通常与泛型一起使用:

type IsString<T> = T extends string ? true : false;

let isString: IsString<number> = false;

6. 索引类型

索引类型用于通过索引访问对象的属性类型:

type Person = {
    name: string;
    age: number;
};

type PersonKey = keyof Person; // "name" | "age"

let key: PersonKey = "name";

7. 可辨识联合

可辨识联合用于描述一组具有共同字段的类型,通常与联合类型结合使用:

interface Circle {
    kind: "circle";
    radius: number;
}

interface Square {
    kind: "square";
    sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape): number {
    switch (shape.kind) {
        case "circle":
            return Math.PI * shape.radius ** 2;
        case "square":
            return shape.sideLength ** 2;
    }
}

这些高级类型提供了强大的工具,用于处理更复杂的类型场景,提高了 TypeScript 在编码时的灵活性和可读性。

十 、类型守卫

在 TypeScript 中,类型守卫(Type Guard)是一种机制,用于在运行时检查变量的类型。这样可以在不同的代码块中根据类型执行不同的操作,同时确保在这些代码块内变量的类型得到正确推断。

1. typeof 类型守卫

使用 typeof 运算符来检查变量的类型。例如:

function printInfo(value: number | string): void {
    if (typeof value === 'number') {
        console.log('It is a number:', value.toFixed(2));
    } else {
        console.log('It is a string:', value.toUpperCase());
    }
}

printInfo(42);     // 输出: It is a number: 42.00
printInfo('hello'); // 输出: It is a string: HELLO

2. instanceof 类型守卫

使用 instanceof 运算符来检查变量是否是某个类的实例。例如:

class Animal {
    move(): void {
        console.log('Moving...');
    }
}

class Bird extends Animal {
    fly(): void {
        console.log('Flying...');
    }
}

function handleAnimal(animal: Animal): void {
    if (animal instanceof Bird) {
        animal.fly();
    } else {
        animal.move();
    }
}

handleAnimal(new Bird()); // 输出: Flying...
handleAnimal(new Animal()); // 输出: Moving...

3. 自定义类型守卫

通过定义自定义函数,称之为类型守卫函数,来进行类型检查。例如:

interface Car {
    brand: string;
    speed: number;
}

function isCar(obj: any): obj is Car {
    return 'brand' in obj && 'speed' in obj;
}

function displayCarInfo(vehicle: Car | { name: string }): void {
    if (isCar(vehicle)) {
        console.log(`Car: ${vehicle.brand}, Speed: ${vehicle.speed} km/h`);
    } else {
        console.log('Not a car');
    }
}

displayCarInfo({ brand: 'Toyota', speed: 120 }); // 输出: Car: Toyota, Speed: 120 km/h
displayCarInfo({ name: 'Bicycle' }); // 输出: Not a car

自定义类型守卫函数返回一个布尔值,用于指示变量是否为特定类型。当返回 true 时,TypeScript 编译器会推断变量为相应的类型。

使用场景

当涉及到类型守卫时,不同的使用场景会涉及到不同的检查和操作。以下是一些常见的使用场景:

1. 表单验证

在处理表单数据时,可能需要根据不同的输入类型执行不同的验证操作。

type FormValue = string | number;

function validateInput(value: FormValue): void {
    if (typeof value === 'string') {
        // 执行字符串验证逻辑
        console.log('Validating string input:', value.length);
    } else if (typeof value === 'number') {
        // 执行数字验证逻辑
        console.log('Validating number input:', value.toFixed(2));
    }
}

validateInput('hello'); // 输出: Validating string input: 5
validateInput(42);      // 输出: Validating number input: 42.00

2. 处理 DOM 事件

当处理 DOM 事件时,可能需要根据事件目标的类型执行不同的操作。

function handleClick(event: Event): void {
    if (event.target instanceof HTMLButtonElement) {
        // 处理按钮点击事件
        console.log('Button clicked:', event.target.textContent);
    } else if (event.target instanceof HTMLInputElement) {
        // 处理输入框点击事件
        console.log('Input clicked:', event.target.value);
    }
}

document.getElementById('button')?.addEventListener('click', handleClick);
document.getElementById('input')?.addEventListener('click', handleClick);

3. 处理不同类型的数据

在处理异构数据结构时,可能需要根据数据的类型执行不同的操作。

interface Car {
    brand: string;
    speed: number;
}

interface Bicycle {
    type: string;
}

function displayInfo(vehicle: Car | Bicycle): void {
    if ('brand' in vehicle && 'speed' in vehicle) {
        console.log(`Car: ${vehicle.brand}, Speed: ${vehicle.speed} km/h`);
    } else if ('type' in vehicle) {
        console.log(`Bicycle: ${vehicle.type}`);
    }
}

displayInfo({ brand: 'Toyota', speed: 120 }); // 输出: Car: Toyota, Speed: 120 km/h
displayInfo({ type: 'Mountain' }); // 输出: Bicycle: Mountain

4. 处理异步操作结果

在处理异步操作的结果时,可能需要根据结果的类型执行不同的处理逻辑。

type ApiResponse = { success: true; data: string } | { success: false; error: string };

function handleApiResponse(response: ApiResponse): void {
    if (response.success) {
        console.log('Success:', response.data);
    } else {
        console.error('Error:', response.error);
    }
}

const successResponse: ApiResponse = { success: true, data: 'Response data' };
const errorResponse: ApiResponse = { success: false, error: 'Error message' };

handleApiResponse(successResponse); // 输出: Success: Response data
handleApiResponse(errorResponse);   // 输出: Error: Error message

这些场景中,使用类型守卫可以使得代码更具可读性,同时保证了类型的正确性,防止在处理不同类型数据时引入潜在的错误。