- 文档
- 教程
【免费下载链接】typescript-book-chinese
TypeScript Deep Dive 中文版
TypeScript 的核心价值在于其静态类型系统。本文基于《深入理解 TypeScript》中文版(typescript-book-chinese)的「类型系统概览」章节,系统讲解从基础类型注解到接口、泛型、联合类型、交叉类型、元组与类型别名的完整知识链,并结合仓库内 docs/typings 系列文档的源码级示例,帮助读者掌握给 JavaScript 代码添加类型并从中受益的全部核心语法,为后续深入理解类型系统(如 类型兼容性、类型推断、环境声明)打下坚实基础。
TypeScript 类型系统的设计哲学
在讨论为什么使用 TypeScript 时,我们需要先理解其类型系统的两个关键设计原则:
- 类型系统是可选的:你的 JavaScript 本身就是合法的 TypeScript,未注解的代码不会报错,这让已有 JS 项目可以零成本起步;
- 类型错误不会阻止 JavaScript 运行:即使存在类型错误,编译产物依然可以执行。这一点让「逐步迁移」成为现实——你可以先把
.js改成.ts,再一块一块地补上类型注解,而不是一次性重写整个项目。
理解这两条原则非常重要,因为它们决定了 TypeScript 的迁移路径是渐进式的、低风险的。在此基础上,本概览将带你学习类型系统的语法:如何给代码加上类型注解,以及能获得哪些实实在在的益处。
基本注解::TypeAnnotation语法
TypeScript 使用:TypeAnnotation语法进行类型注解。只要该内容存在于类型声明空间中,就可以作为类型注解使用。下面的例子同时注解了变量、函数参数和函数返回值:
const num: number = 123; function identity(num: number): number { return num; }这种「注解即文档」的写法,让编译器和阅读代码的人都能明确知道变量与函数的边界约束。更完整的函数参数与返回值注解用法,可参考 docs/typings/functions.md(包括可选参数、默认值与函数重载)。
原始类型:string、number、boolean
JavaScript 的原始类型同样适用于 TypeScript 类型系统,string、number、boolean可以直接用作类型注解。赋值错误类型时会立刻得到编译错误:
let num: number; let str: string; let bool: boolean; num = 123; num = 123.456; num = '123'; // Error str = '123'; str = 123; // Error bool = true; bool = false; bool = 'false'; // Error这里的关键在于:类型注解不是运行时约束,而是编译期的静态检查。错误在编译阶段即被暴露,而不是等到运行时才崩溃。
数组类型:后缀[]语法
TypeScript 为数组提供了专用的类型语法——后缀[],你可以根据需要补充任何有效的类型注解,如:boolean[]。它能让你安全地使用任何数组操作,同时防止把错误类型的值赋给数组成员:
let boolArray: boolean[]; boolArray = [true, false]; console.log(boolArray[0]); // true console.log(boolArray.length); // 2 boolArray[1] = true; boolArray = [false, false]; boolArray[0] = 'false'; // Error boolArray = 'false'; // Error boolArray = [true, 'false']; // Error可以看到,无论是给单个成员赋错类型、把整个数组赋成字符串,还是往数组里塞进一个错误类型的元素,都会被编译器拦截。
接口:合并类型声明
接口(interface)是 TypeScript 的核心知识之一,它能将众多类型声明合并进一个类型声明中,从而强制对每个成员进行类型检查:
interface Name { first: string; second: string; } let name: Name; name = { first: 'John', second: 'Doe' }; name = { // Error: 'Second is missing' first: 'John' }; name = { // Error: 'Second is the wrong type' first: 'John', second: 1337 };在这里,first: string+second: string被合并进了新的类型注解Name,缺字段、字段类型错误都会被编译器拒绝。关于接口更深入的内容(开放合并、implements类实现、声明任意 JS 结构等),见 docs/typings/interfaces.md。
内联类型注解::{ /*Structure*/ }
与创建接口不同,你可以使用内联注解语法注解任何内容::{ /*Structure*/ }:
let name: { first: string; second: string; }; name = { first: 'John', second: 'Doe' }; name = { // Error: 'Second is missing' first: 'John' }; name = { // Error: 'Second is the wrong type' first: 'John', second: 1337 };内联类型的价值在于快速:省去为类型起名的麻烦(尤其是当你可能起一个很糟糕的名字时)。但如果你发现需要多次重复使用同一个内联注解,就应该考虑把它重构为接口或类型别名(type alias,见下文)。
特殊类型:any、null、undefined、void
除原始类型外,TypeScript 还有几个特殊类型,它们在类型系统中扮演着不同的角色。
any:类型系统的「后门」
any在 TypeScript 类型系统中占有特殊地位——它提供了一条类型系统的「后门」,TypeScript 会为标注为any的值关闭类型检查。在类型系统里any兼容所有类型(包括它自己),因此所有类型都能赋值给它,它也能赋值给其他任何类型:
let power: any; // 赋值任意类型 power = '123'; power = 123; // 它也兼容任何类型 let num: number; power = num; num = power;在把 JavaScript 迁移至 TypeScript 的过程中,any会被频繁使用。但你必须尽量减少对它的依赖:使用any基本上等于告诉编译器不要进行任何类型检查,这会削弱类型安全。迁移的最终目标应该是用精确类型逐步替换掉any。
null 和 undefined
在类型系统中,JavaScript 的null和undefined字面量与其他被标注为any类型的变量一样,都能被赋值给任意类型的变量(在strictNullChecks未开启的默认行为下):
// strictNullChecks: false let num: number; let str: string; // 这些类型能被赋予 num = null; str = undefined;需要留意的是:一旦开启strictNullChecks编译选项,null与undefined将不再能随意赋给其他类型,这能捕获大量潜在的空值错误——这是现代 TypeScript 工程中推荐开启的严格模式开关之一。
void:表示没有返回值
使用:void表示一个函数没有任何返回值:
function log(message: string): void { console.log(message); }正如 docs/typings/functions.md 所说明的,通常你可以在函数不返回任何内容时省略void注解,由 TypeScript 自动推导。
泛型:在成员之间提供约束
在计算机科学中,许多算法和数据结构并不依赖于对象的实际类型,但你仍然希望在每个变量上强制提供约束。例如:一个函数接受一个列表并返回这个列表的反向排序,这里的约束就是「传入参数的类型与函数返回值的类型一致」:
function reverse<T>(items: T[]): T[] { const toreturn = []; for (let i = items.length - 1; i >= 0; i--) { toreturn.push(items[i]); } return toreturn; } const sample = [1, 2, 3]; let reversed = reverse(sample); console.log(reversed); // 3, 2, 1 // Safety reversed[0] = '1'; // Error reversed = ['1', '2']; // Error reversed[0] = 1; // ok reversed = [1, 2]; // ok在上例中,函数reverse接受一个类型为T(注意reverse<T>中的类型参数)的数组(items: T[]),返回类型同样为T[]。当你传入const sample = [1, 2, 3]时,TypeScript 会推断出reverse的返回类型为number[],从而提供类型安全。同理,传入string[]时它推断出string[]:
const strArr = ['1', '2']; let reversedStrs = reverse(strArr); reversedStrs = [1, 2]; // Error事实上,JavaScript 数组已经拥有reverse方法,而 TypeScript 正是用泛型来定义其结构的:
interface Array<T> { reverse(): T[]; }这意味着当你调用数组的.reverse()方法时,同样能获得类型安全:
let numArr = [1, 2]; let reversedNums = numArr.reverse(); reversedNums = ['1', '2']; // Error关于Array<T>的更多讨论,会在 docs/typings/ambient.md 的lib.d.ts部分展开。若想深入学习泛型的设计动机(如用泛型类实现类型安全的队列)、泛型误用陷阱与「方便通用」设计模式,可阅读 docs/typings/generices.md。
联合类型:string | number
在 JavaScript 中,你常常希望某个属性是多种类型之一,例如字符串或者数组。这正是 TypeScript 联合类型的用武之地——它使用|作为标记,如string | number。一个典型用例是:一个函数可以接受字符串数组,也可以接受单个字符串:
function formatCommandline(command: string[] | string) { let line = ''; if (typeof command === 'string') { line = command.trim(); } else { line = command.join(' ').trim(); } // Do stuff with line: string }配合typeof收窄(type guard),联合类型让「多种形态输入」的函数既灵活又安全。更系统的类型守卫知识参见 docs/typings/typeGuard.md。
交叉类型:T & U
在 JavaScript 中,extend是一种非常常见的模式:从两个对象创建出一个新对象,新对象拥有两个对象的所有功能。交叉类型(T & U)可以让你安全地使用这种模式:
function extend<T extends object, U extends object>(first: T, second: U): T & U { const result = <T & U>{}; for (let id in first) { (<T>result)[id] = first[id]; } for (let id in second) { if (!result.hasOwnProperty(id)) { (<U>result)[id] = second[id]; } } return result; } const x = extend({ a: 'hello' }, { b: 42 }); // 现在 x 拥有了 a 属性与 b 属性 const a = x.a; const b = x.b;返回类型T & U意味着结果对象同时具备T与U的全部成员,x.a与x.b都能被类型安全地访问。
元组类型:[typeofmember1, typeofmember2]
JavaScript 本身并不支持元组,开发者通常只能用数组表示元组。TypeScript 则原生支持元组类型,你可以使用:[typeofmember1, typeofmember2]的形式为元组添加注解,元组可以包含任意数量的成员:
let nameNumber: [string, number]; // Ok nameNumber = ['Jenny', 221345]; // Error nameNumber = ['Jenny', '221345'];将元组与解构一起使用,可以优雅地取出具名变量:
let nameNumber: [string, number]; nameNumber = ['Jenny', 322134]; const [name, num] = nameNumber;类型别名:type SomeName = someValidTypeAnnotation
TypeScript 提供了为类型注解设置别名的便捷语法:type SomeName = someValidTypeAnnotation。
type StrOrNum = string | number; // 使用 let sample: StrOrNum; sample = 123; sample = '123'; // 会检查类型 sample = true; // Error与接口不同,你可以为任意类型注解提供类型别名,这在联合类型和交叉类型中尤为实用。下面是一些熟悉别名语法的示例:
type Text = string | { text: string }; type Coordinates = [number, number]; type Callback = (data: string) => void;接口还是类型别名:如何选择
官方概览给出如下实践建议:
- 如果你需要使用类型注解的层次结构,请使用接口——接口能使用
implements和extends; - 为简单的对象类型(如上面的
Coordinates)使用类型别名,只需给它一个语义化的名字即可; - 当你想给联合类型或交叉类型提供语义化名称时,类型别名是更好的选择。
值得补充的是,接口还具有「开放合并」的特性(相同名字的接口声明会自动合并),这使其能模仿 JavaScript 对象在运行时被逐步扩展的灵活性,具体见 docs/typings/interfaces.md。
后续学习路线
至此,你已经能够为大部分 JavaScript 代码添加类型注解。接下来可以沿着 docs/typings 目录继续深入 TypeScript 类型系统:
- 接口详解:开放合并、
implements与类的实现约束; - 泛型深入:泛型队列、
getJSON<T>与 axios 配合的实战范式; - 函数类型:参数注解、返回类型、可选参数、默认值与重载;
- 环境声明:用
declare安全使用现有 JavaScript 库; - 类型推断、类型兼容性、类型守卫:理解类型系统更深层的运作机制。
这些章节共同构成了《深入理解 TypeScript》中文版的完整类型系统知识体系,适合从本文的「概览级」知识平滑过渡到「原理级」理解。
- 文档
- 教程
【免费下载链接】typescript-book-chinese
TypeScript Deep Dive 中文版
相关推荐
craft.js的TypeScript高级类型:泛型与接口设计
craft.js的TypeScript高级类型:泛型与接口设计 概述 craft.js作为一个React框架,提供了强大的拖放页面编辑器功能。其TypeScri
前端深入探索 TypeScript 类型系统:《The Concise TypeScript Book》类型系统章全解析
深入探索 TypeScript 类型系统:《The Concise TypeScript Book》类型系统章全解析 本篇指南以开源仓库 The Concise
文档教程Project-Based-Learning类型安全:TypeScript类型系统和泛型
Project Based Learning类型安全:TypeScript类型系统和泛型 引言:为什么JavaScript开发者需要TypeScript? 你是
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考