第三章:基本类型
本章概述
TypeScript 拥有丰富的类型系统,本章将深入讲解基本数据类型,包括原始类型、数组、元组等。理解这些基础类型是掌握 TypeScript 类型系统的关键。
3.1 原始类型(Primitive Types)
3.1.1 boolean 布尔类型
布尔类型只有两种值:true 和 false。
let isDone: boolean = false; let isActive: boolean = true; let hasPermission: boolean = true; // 通过表达式赋值 let isGreater: boolean = 5 > 3; // true let isEqual: boolean = "hello" === "world"; // false // 布尔对象(不推荐) let boolObject: Boolean = new Boolean(true); // 包装对象 let boolPrimitive: boolean = true; // 原始类型,推荐
3.1.2 number 数字类型
TypeScript 中所有数字都是浮点数,支持十进制、十六进制、二进制和八进制字面量。
// 十进制 let decimal: number = 42; let float: number = 3.14159; let negative: number = -10; // 十六进制 let hex: number = 0xff; // 255 let hex2: number = 0xABC; // 2748 // 二进制 let binary: number = 0b1010; // 10 let binary2: number = 0b11110000; // 八进制 let octal: number = 0o744; // 484 // 特殊数值 let infinity: number = Infinity; let notANumber: number = NaN; // 科学计数法 let scientific: number = 1.5e10; // 15000000000 let small: number = 1.5e-10; // 下划线分隔符(ES2021+) let million: number = 1_000_000; let binaryWithSep: number = 0b1111_0000;
3.1.3 string 字符串类型
// 单引号
let singleQuote: string = 'Hello';
// 双引号
let doubleQuote: string = "World";
// 模板字符串(推荐)
let name: string = "Alice";
let greeting: string = `Hello, ${name}!`;
let multiLine: string = `
This is a
multi-line string
`;
// 模板字符串支持表达式
let a: number = 5;
let b: number = 10;
let result: string = `The sum is ${a + b}`;
// 字符串方法
let str: string = "TypeScript";
let upper: string = str.toUpperCase(); // "TYPESCRIPT"
let lower: string = str.toLowerCase(); // "typescript"
let length: number = str.length; // 10
let substring: string = str.substring(0, 4); // "Type"
let includes: boolean = str.includes("Script"); // true
3.1.4 symbol 符号类型
Symbol 是 ES6 引入的原始数据类型,表示唯一的标识符。
// 创建 Symbol
const sym1: symbol = Symbol();
const sym2: symbol = Symbol("description");
const sym3: symbol = Symbol("description");
// 每个 Symbol 都是唯一的
console.log(sym2 === sym3); // false
// 作为对象属性键
const id: symbol = Symbol("id");
const user = {
[id]: 12345,
name: "Alice"
};
// 获取 Symbol 属性需要使用 Object.getOwnPropertySymbols
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)]
// Well-known Symbols
const iterator: symbol = Symbol.iterator;
const asyncIterator: symbol = Symbol.asyncIterator;
const hasInstance: symbol = Symbol.hasInstance;
3.1.5 bigint 大整数类型
BigInt 用于表示任意精度的整数,适合处理超出 number 安全整数范围的数值。
// 创建 BigInt
const big1: bigint = 9007199254740991n;
const big2: bigint = BigInt(9007199254740991);
const big3: bigint = BigInt("900719925474099199999");
// 运算
const sum: bigint = big1 + big2;
const product: bigint = big1 * 2n;
const power: bigint = 2n ** 100n;
// 比较
const isEqual: boolean = big1 === big2;
const isGreater: boolean = big1 > 0n;
// 注意:BigInt 和 number 不能直接混合运算
// const invalid = big1 + 5; // Error
const valid = big1 + 5n; // OK
// 转换
const numFromBig: number = Number(big1); // 可能丢失精度
const strFromBig: string = big1.toString();
3.2 特殊类型
3.2.1 any 任意类型
any 是最灵活的类型,可以赋值为任何类型,绕过类型检查。
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false;
notSure = { name: "Alice" };
notSure = [1, 2, 3];
// 可以访问任何属性
notSure.ifItExists();
notSure.toFixed();
// 隐式 any
// 在 noImplicitAny 关闭时,未注解的变量默认为 any
function log(message) { // 参数类型隐式为 any
console.log(message);
}
// 明确的 any 使用场景
// 1. 第三方库没有类型定义
let thirdPartyData: any = fetchLegacyData();
// 2. 动态内容
let dynamicContent: any = JSON.parse(jsonString);
// 3. 逐步迁移项目
// 可以先用 any,再逐步添加类型
3.2.2 unknown 未知类型
unknown 是类型安全的 any,使用前必须进行类型检查。
let notSure: unknown = 4;
notSure = "hello";
notSure = true;
// 不能直接使用
// notSure.toFixed(); // Error: Object is of type 'unknown'
// 必须使用类型检查
if (typeof notSure === "number") {
console.log(notSure.toFixed(2)); // OK
}
if (typeof notSure === "string") {
console.log(notSure.toUpperCase()); // OK
}
// 类型断言
const value: unknown = "hello";
const str: string = value as string;
// 使用类型守卫函数
function isString(value: unknown): value is string {
return typeof value === "string";
}
if (isString(notSure)) {
console.log(notSure.length); // OK,TypeScript 知道是 string
}
3.2.3 never 永不类型
never 表示永远不会发生的值,用于:
- 抛出异常的函数
- 无限循环的函数
- exhaustive type checking
// 抛出错误的函数返回 never
function throwError(message: string): never {
throw new Error(message);
}
// 无限循环
function infiniteLoop(): never {
while (true) {
console.log("Looping...");
}
}
// exhaustive checking
function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number }
| { kind: "triangle"; base: number; height: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
case "triangle":
return (shape.base * shape.height) / 2;
default:
return assertNever(shape); // 如果 Shape 新增类型,这里会报错
}
}
3.2.4 void 空类型
void 表示没有返回值,通常用于函数。
// 无返回值的函数
function logMessage(message: string): void {
console.log(message);
}
function processData(data: unknown): void {
// 处理数据,不返回结果
saveToDatabase(data);
}
// void 类型的变量只能赋值 undefined 或 null
let unusable: void = undefined;
// unusable = null; // 在 strictNullChecks 下可能报错
// 实际上 void 类型的变量很少使用
3.2.5 null 和 undefined
// undefined
let u: undefined = undefined;
// null
let n: null = null;
// strictNullChecks 开启时的区别
let str: string = "hello";
// str = null; // Error
// str = undefined; // Error
let nullableStr: string | null = null; // OK
nullableStr = "hello"; // OK
let optionalStr: string | undefined = undefined; // OK
optionalStr = "hello"; // OK
// 非空断言(谨慎使用)
function getLength(str: string | null): number {
return str!.length; // ! 告诉 TypeScript str 不为 null
}
// 更安全的做法
function getLengthSafe(str: string | null): number {
return str ? str.length : 0;
}
3.3 数组类型(Array Types)
3.3.1 数组类型注解
// 类型[] 语法
let numbers: number[] = [1, 2, 3, 4, 5];
let strings: string[] = ["a", "b", "c"];
let booleans: boolean[] = [true, false, true];
// Array<类型> 语法(泛型)
let numbers2: Array<number> = [1, 2, 3];
let strings2: Array<string> = ["a", "b", "c"];
// 混合类型数组(不推荐)
let mixed: (string | number)[] = ["a", 1, "b", 2];
// 对象数组
interface User {
id: number;
name: string;
}
let users: User[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
// 多维数组
let matrix: number[][] = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
let threeDArray: number[][][] = [[[1, 2]], [[3, 4]]];
3.3.2 数组方法类型
const numbers: number[] = [1, 2, 3, 4, 5]; // 不改变原数组的方法 const doubled: number[] = numbers.map(n => n * 2); const evens: number[] = numbers.filter(n => n % 2 === 0); const sum: number = numbers.reduce((a, b) => a + b, 0); const found: number | undefined = numbers.find(n => n > 3); const hasEven: boolean = numbers.some(n => n % 2 === 0); const allPositive: boolean = numbers.every(n => n > 0); const index: number = numbers.findIndex(n => n === 3); const sliced: number[] = numbers.slice(1, 3); // 改变原数组的方法 const arr: number[] = [1, 2, 3]; arr.push(4); // 添加元素到末尾 arr.pop(); // 移除末尾元素 arr.unshift(0); // 添加元素到开头 arr.shift(); // 移除开头元素 arr.splice(1, 1); // 删除/插入元素 arr.sort((a, b) => b - a); // 排序 arr.reverse(); // 反转
3.3.3 只读数组
// 使用 readonly 修饰符
const readonlyNumbers: readonly number[] = [1, 2, 3];
// readonlyNumbers.push(4); // Error
// readonlyNumbers[0] = 0; // Error
// 使用 ReadonlyArray 泛型
const readonlyStrings: ReadonlyArray<string> = ["a", "b"];
// 使用 Readonly 工具类型
interface Config {
settings: Readonly<number[]>;
}
// 只读数组可以赋值给普通数组
const arr: readonly number[] = [1, 2, 3];
// const mutable: number[] = arr; // Error
// 普通数组可以赋值给只读数组
const mutable: number[] = [1, 2, 3];
const readonly2: readonly number[] = mutable; // OK
3.3.4 数组类型推断
// 自动推断
const numbers = [1, 2, 3]; // number[]
const strings = ["a", "b"]; // string[]
const mixed = [1, "a", true]; // (string | number | boolean)[]
// 空数组推断为 any[]
const empty = []; // any[]
// 应该显式声明类型
const emptyNumbers: number[] = [];
// 使用 as const 得到字面量类型
const tuple = [1, 2, 3] as const; // readonly [1, 2, 3]
const config = {
ports: [3000, 8080] as const
};
3.4 元组类型(Tuple Types)
3.4.1 基本元组
元组是固定长度、固定类型的数组。
// 定义元组
let person: [string, number] = ["Alice", 25];
// 顺序必须匹配
// let wrong: [string, number] = [25, "Alice"]; // Error
// 长度必须匹配
// person.push("extra"); // 可以编译,但类型系统不限制
// person[2]; // Error: Tuple type '[string, number]' of length '2' has no element at index '2'
// 可选元素
let optionalTuple: [string, number?] = ["Alice"];
optionalTuple = ["Alice", 25]; // OK
// 剩余元素
let restTuple: [string, ...number[]] = ["scores", 85, 90, 95];
3.4.2 命名元组
// 使用类型别名
type Point = [x: number, y: number];
const point: Point = [10, 20];
// 或使用接口(更推荐)
interface Point2 {
x: number;
y: number;
}
const point2: Point2 = { x: 10, y: 20 };
// 具名元组元素(TypeScript 4.0+)
type RGB = [red: number, green: number, blue: number];
const color: RGB = [255, 128, 0];
3.4.3 元组与解构
// 元组解构
const point: [number, number] = [10, 20];
const [x, y] = point;
// 函数返回元组
function getUserInfo(): [string, number] {
return ["Alice", 25];
}
const [name, age] = getUserInfo();
// 交换变量
let a = 1, b = 2;
[a, b] = [b, a];
// 忽略元素
const [first, , third] = [1, 2, 3];
3.4.4 只读元组
// 只读元组 const readonlyTuple: readonly [string, number] = ["Alice", 25]; // readonlyTuple[0] = "Bob"; // Error // 使用 Readonly 工具类型 type ReadonlyPoint = Readonly<[number, number]>;
3.5 枚举类型(Enum)补充
枚举在第四章会详细介绍,这里简单了解其作为基本类型的特性。
// 数字枚举
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
// 字符串枚举
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
// 使用
const dir: Direction = Direction.Up;
const color: Color = Color.Red;
// 常量枚举(编译时内联)
const enum Status {
Active = 1,
Inactive = 0
}
3.6 类型推断与注解的选择
3.6.1 何时添加类型注解
// ✓ 函数参数和返回值
function calculate(x: number, y: number): number {
return x + y;
}
// ✓ 对象属性
interface User {
name: string;
age: number;
}
// ✓ 复杂类型
const config: { apiUrl: string; timeout: number } = {
apiUrl: "https://api.example.com",
timeout: 5000
};
// ✓ 空数组
const numbers: number[] = [];
// ✓ 类属性
class Person {
name: string;
age: number;
}
3.6.2 何时依赖类型推断
// ✓ 简单变量初始化
const name = "Alice"; // string
const age = 25; // number
const isActive = true; // boolean
// ✓ 从函数返回值推断
const result = calculate(1, 2); // number
// ✓ 数组字面量
const colors = ["red", "green", "blue"]; // string[]
// ✓ 对象字面量(简单情况)
const point = { x: 0, y: 0 }; // { x: number; y: number }
3.7 类型兼容性
3.7.1 原始类型兼容性
// 子类型可以赋值给父类型 let num: number = 10; let biggerNum: 10 = 10; num = biggerNum; // OK // biggerNum = num; // Error // 字面量类型 let exact: "hello" = "hello"; let general: string = exact; // OK // exact = general; // Error
3.7.2 数组和元组兼容性
// 元组是数组的子类型 const tuple: [string, number] = ["a", 1]; const arr: (string | number)[] = tuple; // OK // 长度不兼容 // const tuple2: [string, number] = arr; // Error // 数组协变 let strings: string[] = ["a", "b"]; let objects: object[] = strings; // OK(协变)
3.8 实用技巧
3.8.1 类型别名
// 为复杂类型创建别名 type Point = [number, number]; type Point3D = [number, number, number]; type StringOrNumber = string | number; type ID = string | number; type Callback = (error: Error | null, result?: string) => void;
3.8.2 常量断言
// 使用 as const 创建只读字面量类型
const config = {
host: "localhost",
port: 3000,
debug: true
} as const;
// config.port = 8080; // Error: Cannot assign to 'port'
// 数组常量断言
const roles = ["admin", "user", "guest"] as const;
type Role = typeof roles[number]; // "admin" | "user" | "guest"
3.9 本章小结
本章我们学习了:
1. 原始类型 - boolean、number、string、symbol、bigint 2. 特殊类型 - any、unknown、never、void、null、undefined 3. 数组类型 - 声明方式、方法、只读数组 4. 元组类型 - 固定长度数组、命名元组 5. 类型选择 - 何时添加类型注解,何时依赖推断
3.10 练习题
练习 1:类型推断
分析以下代码中变量的推断类型:
const a = 42;
const b = "hello";
const c = [1, 2, 3];
const d = [1, "two", true];
const e = null;
const f = undefined;
const g = { x: 10, y: 20 };
const h = [1, 2] as const;
参考答案
const a: number = 42;
const b: string = "hello";
const c: number[] = [1, 2, 3];
const d: (string | number | boolean)[] = [1, "two", true];
const e: null = null;
const f: undefined = undefined;
const g: { x: number; y: number } = { x: 10, y: 20 };
const h: readonly [1, 2] = [1, 2] as const;
练习 2:数组操作
完成以下数组操作函数的类型定义和实现:
// 1. 过滤出数组中的偶数
function filterEven(numbers: number[]): number[] {
// 实现
}
// 2. 计算数组中所有数字的和
function sumArray(numbers: number[]): number {
// 实现
}
// 3. 将字符串数组转换为大写
function toUpperCaseArray(strings: string[]): string[] {
// 实现
}
// 4. 找出一个数字数组中的最大值和最小值,返回元组
function findMinMax(numbers: number[]): [number, number] {
// 实现
}
参考答案
// 1. 过滤偶数
function filterEven(numbers: number[]): number[] {
return numbers.filter(n => n % 2 === 0);
}
// 2. 计算总和
function sumArray(numbers: number[]): number {
return numbers.reduce((sum, n) => sum + n, 0);
}
// 3. 转换大写
function toUpperCaseArray(strings: string[]): string[] {
return strings.map(s => s.toUpperCase());
}
// 4. 找最大最小值
function findMinMax(numbers: number[]): [number, number] {
if (numbers.length === 0) {
throw new Error("Array is empty");
}
let min = numbers[0];
let max = numbers[0];
for (const n of numbers) {
if (n < min) min = n;
if (n > max) max = n;
}
return [min, max];
}
// 或使用内置方法
function findMinMax2(numbers: number[]): [number, number] {
return [Math.min(...numbers), Math.max(...numbers)];
}
练习 3:类型安全的数据处理
实现一个类型安全的 parseValue 函数:
type ParsedValue = string | number | boolean | null;
function parseValue(input: unknown): ParsedValue {
// 实现:根据 input 的实际类型返回相应的值
// - 如果是 string,直接返回
// - 如果是 number,直接返回
// - 如果是 boolean,直接返回
// - 如果是 null 或 undefined,返回 null
// - 如果是其他类型,返回 null
}
参考答案
type ParsedValue = string | number | boolean | null;
function parseValue(input: unknown): ParsedValue {
if (typeof input === "string") {
return input;
}
if (typeof input === "number") {
return input;
}
if (typeof input === "boolean") {
return input;
}
if (input === null || input === undefined) {
return null;
}
return null;
}
// 测试
console.log(parseValue("hello")); // "hello"
console.log(parseValue(42)); // 42
console.log(parseValue(true)); // true
console.log(parseValue(null)); // null
console.log(parseValue(undefined)); // null
console.log(parseValue({})); // null
练习 4:元组与对象转换
实现以下类型转换函数:
interface Point {
x: number;
y: number;
}
// 1. 将 Point 对象转换为元组 [x, y]
function pointToTuple(point: Point): [number, number] {
// 实现
}
// 2. 将元组 [x, y] 转换为 Point 对象
function tupleToPoint(tuple: [number, number]): Point {
// 实现
}
// 3. 交换元组中的两个元素
function swap<T, U>(tuple: [T, U]): [U, T] {
// 实现
}
参考答案
interface Point {
x: number;
y: number;
}
// 1. 对象转元组
function pointToTuple(point: Point): [number, number] {
return [point.x, point.y];
}
// 2. 元组转对象
function tupleToPoint(tuple: [number, number]): Point {
const [x, y] = tuple;
return { x, y };
}
// 3. 交换元组元素
function swap<T, U>(tuple: [T, U]): [U, T] {
const [first, second] = tuple;
return [second, first];
}
// 测试
const point: Point = { x: 10, y: 20 };
const tuple = pointToTuple(point); // [10, 20]
const backToPoint = tupleToPoint(tuple); // { x: 10, y: 20 }
const swapped = swap(["hello", 42]); // [42, "hello"]