TypeScript 类型缩小

在 TypeScript 中,变量有时可以拥有 联合类型,这意味着它们可能是多种类型之一。例如,

let userInput: string | null = prompt("Enter your name:");

在这里,变量 userInput 可以是字符串或 null。根据其类型,userInput 的行为可能完全不同。

例如,如果 userInputnull,您将无法使用 toUpperCase() 等字符串方法。

那么,如何安全地对类型不确定的变量执行特定于类型的操作呢?这就是 **类型缩小(type narrowing)** 的作用。

什么是类型缩小?

缩小是 TypeScript 根据诸如 if...else 语句之类的代码逻辑,在编译时确定变量特定类型的方式。例如,

function printValue(value: string | number) {

    if (typeof value === "string") {
        
        // TypeScript knows 'value' is a string here
        // So, we can use toUpperCase()
        console.log(value.toUpperCase());
    }
    else {

        // At this point, TypeScript knows 'value' is a number
        console.log(value.toFixed(2));
    }
}

// Pass string argument
printValue("TypeScript");

// Pass number argument
printValue(36.36589);

输出

TYPESCRIPT
36.37

在这里,函数参数 value 是一个联合类型,它可以是字符串或数字。

在我们可以安全地将 valuetoUpperCase()toFixed() 等方法一起使用之前,我们需要确定(或缩小)它的类型。

因此,我们使用 if...else 语句来缩小类型,该语句根据 value 的数据类型确定要执行的操作。

在我们的例子中,当 value 是一个

  • 字符串 - if 语句以大写形式打印 value
  • 数字 - else 语句以小数点后两位的精度打印 value

使用 typeof 进行缩小

我们通常使用 typeof 来缩小字符串、数字、布尔值或符号等原始类型。例如,

function checkInput(input: boolean | string) {
    if (typeof input === "boolean") {
        console.log(`Boolean detected: ${input}`);
    }
    else {
        console.log(`String detected: ${input.toUpperCase()}`);
    }
}

// Pass boolean argument
checkInput(true);

// Pass string argument
checkInput("TypeScript");

输出

Boolean detected: true
String detected: TYPESCRIPT

在这里,我们使用 typeof 来缩小 input 参数的类型。然后程序根据结果执行不同的代码。


使用 Truthy/Falsy 检查进行缩小

缩小的另一种常见方法是检查值是否存在,即它不是 nullundefined。例如,

// Function with optional parameter 'name'
function greet(name?: string) {
    
    // Check if an argument has been passed
    if (name) {
        console.log(`Hello, ${name}!`);
    }
    else {
        console.log("No name provided.");
    }
}

// Provide string argument
greet("James Bond");

// Provide no argument
greet();

输出

Hello, James Bond!
No name provided.

在这个程序中,name 是一个 可选参数,也就是说,在调用 greet() 函数时不需要传递参数(但您可以传递)。

在函数内部,我们使用了类型缩小来检查是否传递了参数。

// Check if an argument has been passed
if (name) {
    console.log(`Hello, ${name}!`);
}
else {
    console.log("No name provided.");
}

当传递参数时,if (name) 求值为真值。如果未传递任何内容,则求值为假值。


使用相等性检查进行缩小

TypeScript 可以使用相等性检查(===, !==)来缩小类型,尤其是在不同的联合成员之间。例如,

function compare(x: string | number, y: string | boolean) {
    if (x === y) {
        // TypeScript knows x and y are both strings
        console.log(`Identical String Arguments: ${x.toUpperCase()}`);
    }
    else {
        console.log("Arguments are not identical in value or type or both.");
    }
}

// Pass two identical string arguments
compare("Saturday", "Saturday");

// Pass different string arguments
compare("Saturday", "Monday");

// Pass a number and a string
compare(7, "Saturday");

// Pass a string and a boolean
compare("Saturday", false);

输出

Identical String Arguments: SATURDAY
Arguments are not identical in value or type or both.
Arguments are not identical in value or type or both.
Arguments are not identical in value or type or both.

在此示例中,我们使用严格相等运算符 === 来缩小参数的类型。

if (x === y) {
    // Code
}
else {
    // Code
}

由于 === 同时比较操作数的值和类型,因此我们知道当 x === ytrue 时,xy 都是字符串。

这使我们能够在该代码块中安全地将 xy 都视为字符串。


使用 in 运算符进行缩小

如果您的类型是具有不同属性的对象,in 运算符可以帮助区分它们。例如,

type Admin = { role: string };
type Guest = { guestToken: string };

function handleUser(user: Admin | Guest) {
    if ("role" in user) {
        console.log("Admin Role:", user.role);
    }
    else {
        console.log("Guest Token:", user.guestToken);
    }
}

// Create object of Admin type
let admin: Admin = { role: "Maintenance" };

// Create object of Guest Type
let guest: Guest = { guestToken : "token1" };

// Pass admin as argument to handleUser()
handleUser(admin);

// Pass guest as argument to handleUser()
handleUser(guest);

输出

Admin Role: Maintenance
Guest Token: token1

上面的程序检查 role 是否存在于 user 中。

  • 如果存在,我们知道 userAdmin 类型。
  • 否则,userGuest 类型。

使用 instanceof 进行缩小

对于类实例或内置对象(如 Date),请使用 instanceof 来检查类型。

示例 1:使用 Date 实例

function dateString(input: Date | string) {
    if (input instanceof Date) {
        console.log(`Year: ${input.getFullYear()}`);
    }
    else {
        console.log(`String: ${input.toLowerCase()}`);
    }
}

// Create a Date object
let today: Date = new Date();

// Pass the Date object as argument
dateString(today);

// Pass a string as argument
dateString("This is a string");

输出

Year: 2025
String: this is a string

在这里,我们使用 instanceof 来确定参数是否是 Date 的实例。

示例 2:使用自定义类

// Create a class
class Admin { 
    constructor(public role: string) {}
}

function instanceString(input: Admin | string) {
    if (input instanceof Admin) {
        console.log(`Admin Role: ${input.role}`);
    }
    else {
        console.log(`String: ${input.toLowerCase()}`);
    }
}

// Create an instance of Admin
let admin: Admin = new Admin("Maintenance");

// Pass the instance as argument
instanceString(admin);

// Pass a string as argument
instanceString("This is a string");

输出

Admin Role: Maintenance
String: this is a string

在这里,我们使用 instanceof 来确定参数是否是 Admin 类的实例。


更多关于类型缩小

为什么 TypeScript 需要类型缩小?

类型缩小很重要,因为它有助于您安全地处理可能具有多种类型的变量。它通过以下方式使您的代码更智能、更安全:

  1. 提前预防错误

您可以检查类型并仅使用匹配的方法。

function print(val: string | number) {
    if (typeof val === "string") {
        // Safe for string
        console.log(val.toUpperCase()); 
    }
}
  1. 在编辑器中获得更好的代码建议

一旦类型被缩小,您就会看到正确的建议。

function check(input: boolean | string) {
    if (typeof input === "string") {
        // Editor knows it's a string
        input.toLowerCase(); 
    }
}
  1. 保持代码整洁安全

您无需强制类型或添加不必要的检查。

function show(val: Date | string) {
    if (val instanceof Date) {
        // Works because it's a Date
        console.log(val.getTime()); 
    }
}
判别联合缩小

当不同类型共享一个共同属性(称为判别项)时,TypeScript 可以使用该属性来缩小类型。例如,

type Circle = { kind: "circle"; radius: number };
type Square = { kind: "square"; size: number };

function area(shape: Circle | Square): number {
    if (shape.kind === "circle") {
        return Math.PI * shape.radius ** 2;
    }
    else {
        return shape.size * shape.size;
    }
}

// Create instances
const circle: Circle = { kind: "circle", radius: 5 };
const square: Square = { kind: "square", size: 4 };

// Call function and log outputs
let circleArea: number = area(circle);
let squareArea: number = area(square);

console.log(`Circle Area: ${circleArea}`);
console.log(`Square Area: ${squareArea}`);

输出

Circle Area: 78.53981633974483
Square Area: 16

该程序使用通用的 kind 属性在 CircleSquare 类型之间进行缩小。然后,它根据形状计算相应的面积。

自定义类型守卫

您可以定义自己的函数来帮助 TypeScript 缩小类型。例如,

// Function to check if argument is a string
function isString(value: unknown): value is string {
    return typeof value === "string";
}

function handle(value: unknown) {
    
    // Use isString() to check for type
    if (isString(value)) {
        console.log(value.toUpperCase());
    }
    else {
        console.log("Not a string.");
    }
}

// Call function with different types
handle("hello world");
handle(42);

输出

HELLO WORLD
Not a string.

在这里,isString() 是一个自定义类型守卫,它告诉 TypeScript 精确的类型,从而可以安全地使用 toUpperCase() 等字符串方法。


另请阅读

你觉得这篇文章有帮助吗?

我们的高级学习平台,凭借十多年的经验和数千条反馈创建。

以前所未有的方式学习和提高您的编程技能。

试用 Programiz PRO
  • 交互式课程
  • 证书
  • AI 帮助
  • 2000+ 挑战