MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

TypeScript中unknown类型的理解与应用

2024-08-044.6k 阅读

1. 认识 unknown 类型

在 TypeScript 的类型体系中,unknown 类型是一种较为特殊的存在。它表示未知类型的值,这意味着我们可以将任何类型的值赋给 unknown 类型的变量。与 any 类型有些相似,但它们有着本质的区别。

any 类型允许对其值进行任意操作,而无需额外的类型检查。例如:

let anyValue: any = "Hello";
anyValue.toUpperCase(); // 可以直接调用字符串的方法,无需担心类型问题
anyValue.push(1); // 即使这个值实际上是字符串,也不会在编译时报错

unknown 类型则不同,当我们有一个 unknown 类型的值时,不能直接对其进行操作,除非我们先进行类型检查。例如:

let unknownValue: unknown = "Hello";
// unknownValue.toUpperCase(); // 这一行会报错,因为 unknown 类型不能直接调用方法

这使得 unknown 类型在安全性上比 any 类型更胜一筹。它在处理一些类型不确定的场景时,既能够接受各种类型的值,又能保证类型安全,防止在运行时出现类型错误。

2. unknown 类型的赋值

2.1 赋值给 unknown 类型

正如前面提到的,任何类型的值都可以赋值给 unknown 类型的变量。这体现了 unknown 类型的包容性。

let num: number = 10;
let str: string = "world";
let bool: boolean = true;
let arr: number[] = [1, 2, 3];
let obj: { name: string } = { name: "John" };

let unknown1: unknown = num;
let unknown2: unknown = str;
let unknown3: unknown = bool;
let unknown4: unknown = arr;
let unknown5: unknown = obj;

在上述代码中,不同类型的变量 numstrboolarrobj 都可以赋值给 unknown 类型的变量 unknown1unknown5

2.2 从 unknown 类型赋值

unknown 类型的值赋给其他类型的变量时,需要进行类型检查或类型断言。如果不进行任何处理,TypeScript 会报错。

let unknownValue: unknown = "Hello";
// let strValue: string = unknownValue; // 这一行会报错,不能直接将 unknown 赋值给 string 类型

要解决这个问题,我们可以使用类型断言。类型断言是一种告诉 TypeScript 编译器“相信我,我知道自己在做什么”的方式。

let unknownValue: unknown = "Hello";
let strValue: string = unknownValue as string;
console.log(strValue.toUpperCase());

在上述代码中,通过 as stringunknownValue 断言为 string 类型,然后可以将其赋值给 strValue 并调用 toUpperCase 方法。

另一种方式是使用 type guards(类型保护)进行类型检查。例如使用 typeof 操作符:

let unknownValue: unknown = "Hello";
if (typeof unknownValue === "string") {
    let strValue: string = unknownValue;
    console.log(strValue.toUpperCase());
}

在这个代码块中,typeof unknownValue === "string" 就是一个类型保护。只有当 unknownValue 确实是字符串类型时,才会进入 if 块,并将其赋值给 strValue 进行操作。

3. 在函数参数和返回值中使用 unknown 类型

3.1 函数参数为 unknown 类型

当函数的参数类型为 unknown 时,我们需要在函数内部对参数进行类型检查,然后才能安全地操作参数。

function printValue(value: unknown) {
    if (typeof value === "string") {
        console.log(value.toUpperCase());
    } else if (typeof value === "number") {
        console.log(value.toFixed(2));
    } else {
        console.log("Unsupported type");
    }
}

printValue("Hello");
printValue(123.456);
printValue(true);

printValue 函数中,首先通过 typeofvalue 进行类型检查,然后根据不同的类型进行相应的操作。如果是字符串类型,将其转换为大写并打印;如果是数字类型,将其保留两位小数并打印;其他类型则打印提示信息。

3.2 函数返回值为 unknown 类型

当函数返回 unknown 类型的值时,调用者在使用返回值前同样需要进行类型检查。

function getValue(): unknown {
    const randomNumber = Math.random();
    if (randomNumber > 0.5) {
        return "Random string";
    } else {
        return 42;
    }
}

let result = getValue();
if (typeof result === "string") {
    console.log(result.length);
} else if (typeof result === "number") {
    console.log(result * 2);
}

getValue 函数中,根据随机数决定返回字符串或数字。调用者在获取返回值 result 后,通过 typeof 检查类型,然后进行相应的操作。

4. 与其他类型的联合和交叉

4.1 与其他类型的联合

unknown 类型与其他类型联合时,结果仍然是 unknown 类型。因为 unknown 表示所有可能的类型,联合其他类型并不会改变其本质。

let unionValue: unknown | string;
// 这里 unionValue 仍然是 unknown 类型,因为 unknown 已经包含了所有可能类型,包括 string
unionValue = 10;
unionValue = "Hello";

4.2 与其他类型的交叉

unknown 类型与其他类型交叉时,结果也是 unknown 类型。这是因为 unknown 类型太宽泛,与其他类型交叉后并没有对其进行有效的约束。

let intersectionValue: unknown & string;
// intersectionValue 仍然是 unknown 类型
intersectionValue = "Hello";
intersectionValue = 10; // 这一行会报错,因为实际上不能将 number 赋值给 unknown & string,这里只是为了说明交叉后行为与 unknown 类似

5. 在泛型中使用 unknown 类型

5.1 泛型函数中的 unknown

在泛型函数中,unknown 类型可以用于限制泛型的类型范围。例如,我们可以定义一个函数,它接受一个 unknown 类型的数组,并对数组中的每个元素进行操作,但前提是先检查元素类型。

function processArray<T>(arr: T[]): void {
    arr.forEach((element: unknown) => {
        if (typeof element === "string") {
            console.log(element.toUpperCase());
        } else if (typeof element === "number") {
            console.log(element * 2);
        }
    });
}

let stringArray: string[] = ["a", "b", "c"];
let numberArray: number[] = [1, 2, 3];

processArray(stringArray);
processArray(numberArray);

processArray 函数中,虽然 arr 是泛型数组,但在遍历数组元素时,将元素视为 unknown 类型,然后通过 typeof 进行类型检查并操作。

5.2 泛型类中的 unknown

在泛型类中,unknown 类型也可以发挥作用。例如,我们可以定义一个泛型类来处理不确定类型的数据。

class DataProcessor<T> {
    private data: T;
    constructor(value: T) {
        this.data = value;
    }
    process(): unknown {
        if (typeof this.data === "string") {
            return this.data.toUpperCase();
        } else if (typeof this.data === "number") {
            return this.data * 2;
        }
        return null;
    }
}

let stringProcessor = new DataProcessor("Hello");
let stringResult = stringProcessor.process();
if (typeof stringResult === "string") {
    console.log(stringResult);
}

let numberProcessor = new DataProcessor(10);
let numberResult = numberProcessor.process();
if (typeof numberResult === "number") {
    console.log(numberResult);
}

DataProcessor 类中,process 方法返回 unknown 类型的值。调用者在获取返回值后,通过类型检查来决定如何处理返回值。

6. 在类型别名和接口中使用 unknown 类型

6.1 类型别名中的 unknown

我们可以在类型别名中使用 unknown 类型,来定义一些包含未知类型部分的数据结构。

type MixedData = {
    id: number;
    info: unknown;
};

let data: MixedData = {
    id: 1,
    info: "Some string info"
};
if (typeof data.info === "string") {
    console.log(data.info.length);
}

data = {
    id: 2,
    info: [1, 2, 3]
};
if (Array.isArray(data.info)) {
    console.log(data.info.length);
}

MixedData 类型别名中,info 字段的类型为 unknown。这使得 info 可以是任何类型,使用时需要进行类型检查。

6.2 接口中的 unknown

接口同样可以使用 unknown 类型来定义不确定类型的属性。

interface MixedObject {
    name: string;
    value: unknown;
}

let obj: MixedObject = {
    name: "Example",
    value: 42
};
if (typeof obj.value === "number") {
    console.log(obj.value * 3);
}

obj = {
    name: "Another",
    value: { key: "value" }
};
if (typeof obj.value === "object" && obj.value!== null) {
    console.log(Object.keys(obj.value).length);
}

MixedObject 接口中,value 属性的类型为 unknown。在使用 obj 时,需要根据 value 的实际类型进行不同的操作。

7. unknown 类型与类型兼容性

7.1 赋值兼容性

如前面所述,任何类型都可以赋值给 unknown 类型,但 unknown 类型不能直接赋值给其他类型,除非经过类型检查或类型断言。这体现了 unknown 类型在赋值兼容性上的单向性。

let num: number = 10;
let unknownValue: unknown = num;
// let newNum: number = unknownValue; // 报错,不能直接将 unknown 赋值给 number

7.2 函数参数兼容性

当函数参数类型为 unknown 时,调用函数时可以传递任何类型的值。但如果函数参数是具体类型,不能传递 unknown 类型的值,除非先进行类型检查或断言。

function takeNumber(num: number) {
    console.log(num * 2);
}

let unknownValue: unknown = 10;
// takeNumber(unknownValue); // 报错,不能将 unknown 类型传递给参数为 number 的函数
if (typeof unknownValue === "number") {
    takeNumber(unknownValue);
}

8. 在实际项目中的应用场景

8.1 处理第三方库的返回值

在使用第三方库时,有时其返回值类型可能不明确。这时可以将返回值类型定义为 unknown,然后进行类型检查来安全地使用。 例如,假设我们有一个第三方函数 fetchData,它返回的数据类型不确定。

function fetchData(): unknown {
    // 模拟从第三方获取数据,这里简单返回一个随机值
    const randomNumber = Math.random();
    if (randomNumber > 0.5) {
        return { name: "John", age: 30 };
    } else {
        return [1, 2, 3];
    }
}

let data = fetchData();
if (typeof data === "object" && data!== null) {
    if ("name" in data && typeof data.name === "string" && "age" in data && typeof data.age === "number") {
        console.log(`Name: ${data.name}, Age: ${data.age}`);
    } else if (Array.isArray(data)) {
        console.log(`Array length: ${data.length}`);
    }
}

在这个例子中,fetchData 返回 unknown 类型的值。通过对返回值进行类型检查,我们可以根据其实际类型进行相应的操作。

8.2 处理用户输入

在前端开发中,用户输入的数据类型往往是不确定的。可以将用户输入先视为 unknown 类型,然后进行类型验证和处理。

// 模拟获取用户输入
let userInput: unknown = prompt("Enter something");
if (typeof userInput === "string") {
    if (/^\d+$/.test(userInput)) {
        let num = parseInt(userInput);
        console.log(`Parsed number: ${num}`);
    } else {
        console.log(`Input is a non - number string: ${userInput}`);
    }
}

在这个代码中,通过 prompt 获取的用户输入被视为 unknown 类型。然后通过 typeof 检查是否为字符串,再进一步验证字符串是否为数字并进行相应处理。

8.3 通用的数据处理函数

在一些通用的数据处理函数中,unknown 类型可以提供灵活性。例如,我们可以定义一个函数来处理不同类型的数据并进行日志记录。

function logData(data: unknown) {
    if (typeof data === "string") {
        console.log(`String data: ${data}`);
    } else if (typeof data === "number") {
        console.log(`Number data: ${data}`);
    } else if (Array.isArray(data)) {
        console.log(`Array data with length ${data.length}`);
    } else if (typeof data === "object" && data!== null) {
        console.log(`Object data with keys: ${Object.keys(data).join(", ")}`);
    } else {
        console.log("Unknown data type");
    }
}

logData("Hello");
logData(123);
logData([1, 2, 3]);
logData({ key: "value" });
logData(true);

logData 函数中,通过对 unknown 类型的 data 进行不同类型的检查,实现了对多种类型数据的通用处理和日志记录。

通过以上对 unknown 类型的深入理解和应用示例,我们可以看到 unknown 类型在 TypeScript 中为处理不确定类型的数据提供了一种安全且灵活的方式,帮助我们编写更健壮的代码。无论是在处理第三方库、用户输入还是构建通用函数和数据结构时,合理使用 unknown 类型都能有效提升代码的质量和可维护性。