返回 导航

其他

hangge.com

TypeScript - 数据类型详解2(TS特有类型:any、unknown、void、never、tuple)

作者:hangge | 2026-07-31 08:49
    除了继承 JavaScript 的数据类型外,TypeScript 还引入了一些特有的数据类型,用于处理更复杂的类型场景。这些类型包括 anyunknownvoidnevertuple。本文将详细介绍这些 TypeScript 特有数据类型的使用方法、应用场景以及最佳实践,帮助大家在实际开发中选择合适的类型。

一、any 类型

1,基本使用

(1)在某些情况下,我们难以确定变量的类型,且类型可能会发生变化。这时可以使用 any 类型(类似于 Dart 语言中的 dynamic 类型)。any 类型相当于一种讨巧的 TypeScript 手段,它允许我们绕过类型检查:
// any 类型可以赋任意值
let message: any = "Hello World";

message = 123;      // 可以赋值为 number
message = true;     // 可以赋值为 boolean
message = {};       // 可以赋值为 object

console.log(message);

(2)any 类型也可以用于数组,表示数组可以存储任意类型的数据:
注意:虽然 any 类型很方便,但它会完全禁用类型检查,失去了 TypeScript 的类型安全优势。在实际开发中应尽量避免使用 any 类型。
// any 类型数组
const arr: any[] = [];

arr.push("hello");   // 字符串
arr.push(123);       // 数字
arr.push(true);      // 布尔值

console.log(arr);  // ["hello", 123, true]

2,any 类型的特点

(1)any 类型具有以下特点:
  • 可以对 any 类型的变量进行任何操作,包括获取不存在的属性和方法
  • 可以为 any 类型的变量赋任意值
  • any 类型可以赋值给任何其他类型
// any 类型的特点
let data: any = "hello";

// 可以调用任何方法(即使不存在)
console.log(data.toUpperCase());  // HELLO
console.log(data.foo());          // 运行时报错,但编译不报错

// any 可以赋值给任何类型
let str: string = data;  // 不报错
let num: number = data;  // 不报错

(2)any 类型通常用于以下场景(但应尽量缩小 any 的使用范围):
  • 引入第三方库时缺少类型声明;
  • 类型注解过于烦琐时临时使用;
  • JavaScript 迁移代码时逐步添加类型。

二、unknown 类型

1,基本使用

(1)unknownTypeScript 中一种特殊的类型,用于描述类型不确定的变量。与 any 不同,unknown 是类型安全的:
提示unknown 类型被称为安全的 any。当你不确定变量的具体类型时,应该优先使用 unknown 而非 any,这样可以强制进行类型检查后再使用。
// unknown 类型示例
function foo(): string {
  return "abc";
}

function bar(): number {
  return 123;
}

let flag = true;
let result: unknown;  // 可接收任意类型

if (flag) {
  result = foo();   // 接收 string 类型
} else {
  result = bar();   // 接收 number 类型
}

console.log(result);  // abc

(2)unknown 类型的一个重要特点是:它只能赋值给 anyunknown 类型,不能直接赋值给其他类型:
let result: unknown = "hello";

// 错误:unknown 不能赋值给 string
let str: string = result;  // Type 'unknown' is not assignable to type 'string'.

// 错误:unknown 不能赋值给 number
let num: number = result;  // Type 'unknown' is not assignable to type 'number'.

// 正确:unknown 可以赋值给 any
let data: any = result;

// 正确:unknown 可以赋值给 unknown
let value: unknown = result;

2,类型收窄

(1)要使用 unknown 类型的变量,必须先进行类型检查或类型断言:
let result: unknown = "Hello TypeScript";

// 方式一:类型检查
if (typeof result === "string") {
  console.log(result.toUpperCase());  // HELLO TYPESCRIPT
}

// 方式二:类型断言
console.log((result as string).length);  // 16

// 方式三:类型守卫函数
function isString(value: unknown): value is string {
  return typeof value === "string";
}

if (isString(result)) {
  console.log(result.toLowerCase());  // hello typescript
}

(2)unknown 类型在使用前必须进行类型收窄,这是与 any 的核心区别:
特点 any unknown
类型安全 不安全,跳过所有检查 安全,使用前必须检查
属性访问 可以访问任意属性 不能直接访问属性
赋值给其他类型 可以赋值给任何类型 只能赋值给 any/unknown
使用场景 临时方案、快速迁移 不确定类型时的首选

三、void 类型

1,基本使用

(1)void 类型通常用于表示函数没有返回值:
// 没有返回值的函数,默认返回 void
function sum(num1: number, num2: number) {
  console.log(num1 + num2);
}

// 显式指定返回类型为 void
function greet(name: string): void {
  console.log(`Hello, ${name}!`);
}

sum(10, 20);     // 30
greet("World");  // Hello, World!

(2)void 类型的函数可以返回 nullundefined
注意:在严格模式(strict: true)下,void 类型函数不能返回 null,只能返回 undefined 或不返回。
// void 类型可以返回 null 或 undefined
function returnNull(): void {
  return null;       // 允许(需关闭 strictNullChecks)
}

function returnUndefined(): void {
  return undefined;  // 允许
}

function returnNothing(): void {
  // 没有返回语句,隐式返回 undefined
}

2,void 与 undefined 的区别

(1)voidundefined 的主要区别在于语义:
// void 表示函数没有返回值(不关心返回什么)
function logMessage(msg: string): void {
  console.log(msg);
}

// undefined 表示函数返回 undefined 值
function getUndefined(): undefined {
  return undefined;
}

(2)在变量声明中,void 类型只能赋值为 nullundefined
提示void 类型主要用于函数返回值类型声明,很少用于变量声明。
// 声明 void 类型的变量
let unusable: void = undefined;

// 在严格模式下,null 也不能赋值给 void
// unusable = null;  // Error if strictNullChecks is true

四、never 类型

1,基本使用

(1)never 类型表示永远不会有返回值的类型。如果一个函数陷入死循环或抛出异常,那么它的返回类型就是 never
提示never 类型通常用于:1)表示永远不会返回的函数;2)进行 exhaustive check(完整性检查);3)表示不可能出现的情况。
// never 类型:死循环
function infiniteLoop(): never {
  while (true) {
    console.log("Running...");
  }
}

// never 类型:抛出异常
function throwError(message: string): never {
  throw new Error(message);
}

// 调用
// infiniteLoop();  // 永远不会结束
throwError("Something went wrong");  // 抛出异常

(2)never 类型是所有类型的子类型,但没有任何类型是 never 的子类型:
// never 可以赋值给任何类型
let neverValue: never;

let str: string = neverValue;    // 正确
let num: number = neverValue;    // 正确
let bool: boolean = neverValue;  // 正确

// 但没有任何值可以赋值给 never
// neverValue = "hello";  // 错误
// neverValue = 123;      // 错误

2,完整性检查(Exhaustive Check)

(1)never 类型的一个重要应用是进行 switch 语句的完整性检查:
function handleMessage(message: number | string) {
  switch (typeof message) {
    case 'string':
      console.log('String:', message);
      break;
    case 'number':
      console.log('Number:', message);
      break;
    default:
      // 完整性检查:如果还有未处理的类型,这里会报错
      const check: never = message;
      break;
  }
}

handleMessage(123);   // Number: 123
handleMessage("abc"); // String: abc

(2)如果修改参数类型但忘记添加对应的 case 分支,TypeScript 会报错提醒。这种机制可以帮助我们在修改代码时发现遗漏的情况,确保所有类型分支都被处理。
// 修改参数类型,增加 boolean
function handleMessage2(message: number | string | boolean) {
  switch (typeof message) {
    case 'string':
      console.log('String:', message);
      break;
    case 'number':
      console.log('Number:', message);
      break;
    default:
      // 错误:boolean 类型不能赋值给 never
      const check: never = message;  // Type 'boolean' is not assignable to type 'never'.
      break;
  }
}

五、tuple 类型

1,基本使用

(1)tuple(元组)类型表示多个元素的组合,可以指定数组中每个元素的类型:
// 元组类型:指定每个元素的类型
const info: [string, number, number] = ["hangge", 88, 1.88];
 
// 通过索引访问,有类型提示
const name = info[0];  // 类型为 string
const age = info[1];   // 类型为 number
const height = info[2]; // 类型为 number
 
console.log(name);    // hangge
console.log(age);     // 88
console.log(height);  // 1.88

(2)元组与数组的区别:
  • 数组通常存放相同类型的元素,而元组可以存放不同类型的元素
  • 元组中每个元素都有固定的类型,通过索引获取时有明确的类型提示
  • 元组的长度是固定的,而数组长度可变
注意:元组类型虽然灵活,但元素数量是固定的。如果需要动态添加元素,应该使用数组类型。
// 数组:所有元素类型相同
const arr: any[] = ["hangge", 88, 1.88];
const name = arr[0];  // 类型为 any,没有明确提示
 
// 元组:每个元素类型明确
const tuple: [string, number, number] = ["hangge", 88, 1.88];
const name2 = tuple[0];  // 类型为 string,有明确提示
console.log(name2.length);  // 可以调用 string 方法

2,元组的应用场景

(1)元组类型常用于函数返回多个值的场景。例如模拟 ReactuseState Hook
// 定义返回元组:[总和: number, 平均值: number]
function calcSumAndAvg(nums: number[]): [number, number] {
  const sum = nums.reduce((a, b) => a + b, 0);
  const avg = sum / nums.length;
  // 返回固定顺序、固定类型的元组
  return [sum, avg];
}

// 解构元组接收多个返回值
const [total, average] = calcSumAndAvg([10, 20, 30]);

console.log(total);    // 60
console.log(average);  // 20

(2)元组也可以用于表示坐标点、键值对等固定结构的数据:
提示:在实际开发中,配合泛型使用元组可以获得更好的类型提示。例如 useState<T> 可以让返回值具有具体的类型。
// 坐标点
const point: [number, number] = [100, 200];
const [x, y] = point;
console.log(`坐标: (${x}, ${y})`);  // 坐标: (100, 200)

// 键值对
const entry: [string, number] = ["score", 95];
const [key, value] = entry;
console.log(`${key}: ${value}`);  // score: 95

3,可选元素与只读元组

(1)元组支持可选元素,使用 ? 标记:
// 可选元素
let tuple: [string, number?, boolean?];

tuple = ["hello"];           // 正确
tuple = ["hello", 123];      // 正确
tuple = ["hello", 123, true]; // 正确

(2)可以使用 readonly 关键字定义只读元组:
// 只读元组
const readonlyTuple: readonly [string, number] = ["hello", 123];

// 错误:不能修改只读元组
// readonlyTuple[0] = "world";  // Cannot assign to '0' because it is a read-only property.
// readonlyTuple.push("more");  // Property 'push' does not exist on type 'readonly [string, number]'.

附、TypeScript 特有类型速查表

(1)下表汇总了 TypeScript 特有数据类型的特点和使用场景:
类型 说明 使用场景
any 任意类型,跳过类型检查 临时方案、第三方库迁移
unknown 安全的任意类型 不确定类型时的首选
void 没有返回值 函数返回值类型
never 永不存在的类型 死循环、异常、完整性检查
tuple 固定长度和类型的数组 多返回值、固定结构数据

(2)类型选择建议:
  • 优先使用具体类型,获得完整的类型提示和检查
  • 不确定类型时使用 unknown,而非 any
  • 函数没有返回值时使用 void
  • 使用 never 进行完整性检查,确保所有分支都被处理
  • 需要返回多个不同类型值时使用 tuple
评论

全部评论(0)

回到顶部