Dark Dwarf Blog background

TypeScript 基础类型

TypeScript 基础类型

1. 原始数据类型

JavaScript 的原始数据类型包括:booleannumberstringnullundefinedsymbolbigint。TypeScript 为这些类型都提供了相应的类型注解。

a.a. 布尔值 (boolean)

使用 boolean 定义布尔值类型。

let isDone: boolean = false;
let isVisible: boolean = true;

注意: boolean 是原始类型,而 Boolean 是一个包装对象。应始终使用 boolean

let createdByNewBoolean: boolean = new Boolean(1);
// Error: Type 'Boolean' is not assignable to type 'boolean'.

b.b. 数值 (number)

使用 number 定义所有数字类型,包括整数、浮点数,以及 ES6+ 的二进制、八进制、十六进制字面量。

let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;

c.c. 字符串 (string)

使用 string 定义字符串类型。推荐使用模板字符串来拼接多行文本或嵌入变量。

let myName: string = "Alice";
let myAge: number = 30;

// 模板字符串
let sentence: string = `Hello, my name is ${myName}.
I'll be ${myAge + 1} years old next year.`;

d.d. Null 和 Undefined

nullundefined 各自拥有自己的类型,名为 nullundefined

let u: undefined = undefined;
let n: null = null;

默认情况下,nullundefined 是所有类型的子类型,可以把它们赋值给 number 等类型的变量。但在实际开发中,这通常是错误的根源。强烈推荐开启 strictNullChecks 编译选项。开启后,nullundefined 只能赋值给 voidanyunknown 和它们各自的类型。这能有效避免大量的潜在运行时错误。

// tsconfig.json --> "strictNullChecks": true

let num: number = undefined; // Error: Type 'undefined' is not assignable to type 'number'.
let str: string = null; // Error: Type 'null' is not assignable to type 'string'.

// 只在需要时,通过联合类型显式允许 null 或 undefined
let nullableString: string | null = "hello";
nullableString = null; // OK

e.e. void

void 用于表示没有任何返回值的函数。声明一个 void 类型的变量没有实际用途。

function logMessage(message: string): void {
  console.log(message);
  // 没有 return 语句
}

f.f. Symbol

symbol 是 ES6 引入的一种原始数据类型,它的主要特点是唯一且不可变。每次调用 Symbol() 都会创建一个全新的、独一无二的值。

const sym1 = Symbol("key");
const sym2 = Symbol("key");

console.log(sym1 === sym2); // false,即使描述相同,值也不同

symbol 最核心的用途是创建唯一的对象属性键,以避免属性名冲突。这在多人协作或扩展第三方库对象时尤其有用。

const userID = Symbol("id");

let user = {
  name: "Alice",
  [userID]: "12345", // 使用 symbol 作为属性键
};

console.log(user.name); // 'Alice'
console.log(user[userID]); // '12345'

// Symbol 属性默认是不可枚举的
console.log(Object.keys(user)); // ['name']
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)]
for (const key in user) {
  console.log(key); // 只会打印 'name'
}

g.g. BigInt

bigint 是 ES2020 引入的一种数值类型,用于表示超出 number 类型安全整数范围(Number.MAX_SAFE_INTEGER)的极大整数。它通过在整数字面量后添加 n 来定义。

const big: bigint = 100n;
const alsoBig: bigint = BigInt("9007199254740991");

// 注意:bigint 和 number 不是同一类型,不能混合运算
// const mixed = big + 10; // Error: Operator '+' cannot be applied to types 'bigint' and 'number'.

2. Any 类型

any 类型是 TypeScript 的一个“后门”,它允许我们绕过编译时的类型检查。

一个被标记为 any 的变量可以被赋予任何类型的值。

let myFavoriteNumber: any = "seven";
myFavoriteNumber = 7; // OK

any 类型的变量进行任何操作(访问属性、调用方法)都是允许的,并且返回的值也是 any 类型。这被称为 any 的“传染性”,它会污染代码库的类型安全。

let anyThing: any = "hello";
console.log(anyThing.nonExistentProperty); // 不会报错
anyThing.someMethod(); // 不会报错

let result = anyThing.foo(); // result 的类型也是 any

unknown 类型是 TypeScript 3.0 引入的,作为 any 的类型安全对应物。任何值都可以赋给 unknown,但 unknown 类型的值不能赋给除了 anyunknown 之外的任何类型。在使用 unknown 类型的值之前,必须进行类型收窄(如类型检查或断言)。

let notSure: unknown = 4;
notSure = "maybe a string instead";

let aNumber: number;

// aNumber = notSure; // Error: Type 'unknown' is not assignable to type 'number'.

// 必须先进行类型检查
if (typeof notSure === "number") {
  aNumber = notSure; // OK
}

最佳实践: 只有在万不得已时才使用 any。在大多数情况下,unknown 是一个更安全的选择。

3. 联合类型 (Union Types)

联合类型表示一个值可以是几种类型之一,使用 | 分隔。

let value: string | number;
value = "hello"; // OK
value = 123; // OK
// value = false; // Error

当不确定联合类型变量的具体类型时,只能访问所有类型共有的属性或方法。为了使用特定类型的属性或方法,需要进行类型收窄

function getLength(something: string | number): number {
  // return something.length; // Error: Property 'length' does not exist on type 'number'.

  // 使用 typeof 进行类型收窄
  if (typeof something === "string") {
    return something.length; // OK, something 在这里被认为是 string
  } else {
    return something.toString().length; // OK, something 在这里被认为是 number
  }
}

4. 数组与元组

a.a. 数组 (Array)

有多种方式定义数组:

  1. 类型 + 方括号: let list: number[] = [1, 2, 3]; (最常用)
  2. 数组泛型: let list: Array<number> = [1, 2, 3];
  3. 接口: interface NumberArray { [index: number]: number; } (不常用)

数组中的元素必须与定义的类型一致。

b.b. 元组 (Tuple)

元组允许我们定义一个已知元素数量和类型的数组,各元素的类型不必相同。

// 定义一个包含 string 和 number 的元组
let x: [string, number];

x = ["hello", 10]; // OK
// x = [10, 'hello']; // Error

console.log(x[0].substring(1)); // OK
// console.log(x[1].substring(1)); // Error: Property 'substring' does not exist on type 'number'.

5. 函数类型

a.a. 函数定义

为函数添加类型注解,需要同时为参数和返回值指定类型。

// 函数声明
function add(x: number, y: number): number {
  return x + y;
}

// 函数表达式
const subtract: (base: number, decrementor: number) => number = function (
  x,
  y,
) {
  return x - y;
};

在函数表达式中,(base: number, decrementor: number) => number 就是完整的函数类型定义。

b.b. 可选、默认和剩余参数

  • 可选参数: 使用 ? 标记,必须位于必需参数之后。
  • 默认参数: 为参数提供默认值,该参数会被视为可选。
  • 剩余参数: 使用 ... 语法将多个参数收集到一个数组中。
// lastName 是可选参数
function buildName(firstName: string, lastName?: string) {
  return lastName ? `${firstName} ${lastName}` : firstName;
}

// a 和 b 都有默认值
function calculate(a: number = 10, b: number = a / 2) {
  return a + b;
}

// ...items 是剩余参数,它是一个数组
function sum(...items: number[]): number {
  return items.reduce((total, current) => total + current, 0);
}

c.c. 重载

重载允许一个函数根据不同的参数类型或数量返回不同的结果类型。它通过提供多个函数签名和一个通用的实现体来工作。

// 重载签名
function reverse(x: number): number;
function reverse(x: string): string;

// 实现签名(对外部不可见)
function reverse(x: number | string): number | string {
  if (typeof x === "number") {
    return Number(x.toString().split("").reverse().join(""));
  } else {
    return x.split("").reverse().join("");
  }
}

const numResult = reverse(123); // numResult 的类型是 number
const strResult = reverse("abc"); // strResult 的类型是 string