- 教程
【免费下载链接】typescript-book
:books: The definitive guide to TypeScript and possibly the best TypeScript book :book:. Free and Open Source 🌹
本文是 typescript-book 项目中「错误处理」章节(docs/errors/common-errors.md)的完整实战讲解,聚焦开发者日常开发中最常遇到的几类 TypeScript 编译错误——未声明全局变量、缺失模块声明、模块编译开关缺失、catch 子句类型标注以及 React 声明文件重复等。读完本文,你将掌握每类错误的根因、识别方法与标准修复流程,并能结合 docs/errors/interpreting-errors.md 中介绍的"错误解读方法",在 IDE 中快速定位并修复自己的类型错误。
一、先建立全局观:TypeScript 错误信息是怎么组织的
TypeScript 是一门以"开发者帮助"为核心设计导向的编程语言,因此它的错误信息通常分为两个层次(详见 docs/errors/interpreting-errors.md):
- 简洁版(Succinct):一行式描述,包含错误码与概要信息,例如
TS2345: Argument of type ... is not assignable to parameter of type ...,便于用错误码搜索同类问题。 - 详细版(Detailed):在简洁版基础上追加一条"WHY?"因果链,逐层拆解类型不兼容的根源,例如
Types of property 'bar' are incompatible.之后继续追问Type '() => string' is not assignable to type 'string'.。
IDE 的悬浮提示通常同时展示这两种形式,日常排错时应主要阅读详细版并在脑中形成因果链。而本文要讲的 docs/errors/common-errors.md 则汇总了真实世界中最高频出现的几组错误码,下面逐一展开。
二、TS2304:Cannot find name(找不到名字)
错误示例:
Cannot find name 'ga' Cannot find name '$'根因:你正在使用某个第三方库(例如 Google Analytics 的ga、jQuery 的$),但该库没有被声明(declare)。TypeScript 之所以报错,是它想帮你拦截两类低级问题:
- 拼写错误;
- 未声明就使用变量。
因此,凡是因为引入外部库而在运行时确实存在的"全局名字",你都必须显式地告诉 TypeScript,即编写环境声明(ambient declaration)。
在 code/errors/common-errors.ts 中,就保留着这个错误的原始复现:
ga(); import {debounce} from "underscore";在没有声明的情况下编译(该项目使用 code/errors/tsconfig.json,仅开启"noEmit": true做类型检查),第一行就会触发Cannot find name 'ga'。
如何修复:使用 declare 关键字
在 docs/types/ambient/d.ts.md 中给出了最直接的对照:
foo = 123; // Error: `foo` is not defineddeclare var foo: any; foo = 123; // allowed关于声明文件有几点重要约定:
- 环境声明既可以写在
.ts文件中,也可以写在.d.ts文件中;真实项目强烈建议使用独立的.d.ts文件(例如命名为global.d.ts或vendor.d.ts)。 - 如果文件扩展名是
.d.ts,那么每个根级定义都必须带declare前缀。这提醒作者:TypeScript 不会为该文件发射任何代码,作者必须自行保证被声明的实体在运行时真实存在。 - 环境声明本质上是"与编译器达成的承诺":如果运行时并不存在这些实体却强行使用,程序会在运行时静默出错。同时它也是一份"文档",源码变化后若声明未同步更新,就会出现"运行时正常但编译报错"的中间状态(见 docs/types/ambient/intro.md)。
推荐写法:能用 interface 就不要用裸的any。例如 docs/types/ambient/variables.md 中对process变量的声明:
interface Process { exit(code?: number): void; } declare var process: Process;这样其他开发者可以通过 interface 合并(declaration merging)对全局变量进行扩展,例如追加exitWithLogging方法,同时仍保持类型安全。
更多修复路径
- 如果该库已在 @types 生态(如 DefinitelyTyped)中提供类型,优先
npm install --save-dev @types/库名,并把声明文件纳入编译上下文(参见 docs/types/ambient/d.ts.md 与 docs/project/compilation-context.md)。 - 若引入的是项目自身的全局脚本,则在自己维护的
global.d.ts中补上声明。
三、TS2307:Cannot find module(找不到模块)
错误示例:
Cannot find module 'underscore'根因:你以模块方式使用某个第三方库(例如import * as _ from 'underscore'或import {debounce} from "underscore"),但缺少对应的环境声明文件(.d.ts)。TS2304 针对的是"全局变量"场景,而 TS2307 针对的是"模块导入"场景——两者唯一的差别在于使用方式,修复思路一脉相承:为库提供类型声明。
同样在 code/errors/common-errors.ts 中,import {debounce} from "underscore";一行就是 TS2307 的复现样本。
理解模块与全局命名空间
为什么同样一个库,写法不同错误就不同?根源在 TypeScript 的模块体系(docs/project/modules.md):
- 全局模块:默认情况下,新 TypeScript 文件中的代码处于全局命名空间。文件
foo.ts里写了var foo = 123;,另一个文件bar.ts里就能直接使用foo(尽管这有命名冲突风险)。 - 文件模块(外部模块):一旦文件根级出现
import或export,该文件就拥有了局部作用域,不再污染全局。此时若想在bar.ts使用foo.ts导出的内容,必须显式导入:
// foo.ts export var foo = 123; // bar.ts import { foo } from "./foo"; var bar = foo; // allowedTS2307 正是"导入语句存在,但编译器找不到该模块的类型描述"时的报错。
修复方式
- 安装官方/社区类型包:对 underscore 这类流行库,
npm install --save-dev @types/underscore即可(参考 docs/types/@types.md)。 - 为模块编写环境声明:若没有现成类型,可在
.d.ts中声明模块(详见 docs/types/ambient/d.ts.md):
declare module "underscore" { // 这里描述 underscore 的导出结构 }- 注意把声明文件放入编译上下文(docs/project/compilation-context.md),否则声明不会生效。
四、TS1148:Cannot compile modules unless the '--module' flag is provided
错误示例:
Cannot compile modules unless the '--module' flag is provided根因:代码中使用了外部模块(文件含根级import/export),但编译时没有指定--module标志,编译器不知道要把模块编译成哪种 JavaScript 模块格式。
修复:为编译命令补上模块标志,或在tsconfig.json的compilerOptions中显式配置module字段。module的取值包括commonjs、amd、system、umd、es2015/esnext等,具体选择取决于目标运行环境:
{ "compilerOptions": { "module": "commonjs", "target": "es5" } }说明:
module标志决定的是 TypeScript 文件编译后生成什么样的 JavaScript 模块语法,它与 docs/project/modules.md 中讨论的"文件模块"概念直接相关——有模块,就必须告诉编译器输出格式。Node.js 环境通常用commonjs,浏览器打包场景视构建工具而定。
为什么本项目示例没遇到 TS1148?
注意 code/errors/tsconfig.json 只设置了"noEmit": true,其用途是只做类型检查、不输出 JS。在noEmit模式下编译器不发射代码,因此不会强制要求module标志;但一旦你需要实际编译输出(例如tsc生成 JS 文件),就必须配置module。
五、Catch 子句变量不能带类型标注
错误示例:
try { something(); } catch (e: Error) { // Catch clause variable cannot have a type annotation }根因:JavaScript 规范规定catch子句的变量不携带类型标注。TypeScript 在这里"保护"你免受现实世界中各类不规范 JS 代码的坑——你无法确定运行时 catch 到的究竟是不是Error实例,因此类型系统不允许你武断地给e标注类型。
修复:改用类型守卫(type guard)。捕获后再用instanceof判断:
try { something(); } catch (e) { if (e instanceof Error){ // Here you go. 此时 e 被收窄为 Error 类型 } }instanceof是一种内建的类型守卫(type guard),能够把e从unknown/any收窄为Error。关于类型守卫的更多用法可参考 docs/types/typeGuard.md。
补充:在较新的 TypeScript 版本中,未标注的
catch (e)变量默认类型为unknown(当useUnknownInCatchVariables开启时),这意味着在判断之前直接访问e.message等属性本身就会报错——这恰好强化了"先守卫、再使用"的正确姿势。
六、InterfaceElementClasscannot simultaneously extend typesComponentandComponent
错误示例:
Interface 'ElementClass' cannot simultaneously extend types 'Component' and 'Component'根因:编译上下文中出现了两个react.d.ts(即两份@types/react/index.d.ts),TypeScript 无法决定该采用哪一份,于是报出"同一个接口同时扩展了两个同名类型"的错误。
标准修复流程:
- 重装依赖:删除
node_modules,删除package-lock.json(或 yarn.lock),然后重新执行npm install(或对应包管理器命令),以消除重复/残留的类型包。 - 若仍无效:找出那个"非法的模块"——项目中所有依赖
react的模块都应把@types/react声明为peerDependency(对等依赖),而不是硬编码的dependency。某个包如果把@types/react写进了自己的dependencies,就会导致重复安装。找到该模块后,应到其项目仓库反馈问题,要求其修正依赖声明方式。
为什么重复声明会引发这个错误
TypeScript 的**接口合并(declaration merging)**机制允许同名 interface 合并,但extends一个"模棱两可"的基类时,编译器必须能解析出唯一的类型来源。当node_modules中存在两份@types/react/index.d.ts(例如版本冲突或依赖树嵌套导致重复拷贝)时,类型解析出现歧义,合并规则被破坏,于是报出"同时扩展两个 Component"的错误。这也提示我们:保持依赖树的干净(单一版本)对 TypeScript 类型解析至关重要。
七、结合 IDE 的完整排错流程
把上面的错误知识串起来,一套可复用的排错流程是:
- 阅读详细版错误,在脑中形成 "ERROR → WHY? → CAUSE ERROR → WHY? → ..." 的因果链,定位到具体属性或表达式(方法见 docs/errors/interpreting-errors.md)。
- 记录错误码(如
TS2304、TS2307),用错误码搜索同类问题的资料,命中率高且信息噪音小。 - 对照本文的分类表判断根因类别:
- 全局名字未声明 → 补环境声明(TS2304);
- 模块缺少类型声明 → 安装 @types 或写
declare module(TS2307); - 模块未配置编译格式 → 配置
module(TS1148); - catch 变量带标注 → 去掉标注改用类型守卫;
- React 相关重复声明 → 重装依赖、检查 peerDependency。
项目源码中,code/errors/common-errors.ts 与 code/errors/interpreting-errors.ts 分别保留了两类错误的最小复现样本(前者演示 TS2304/TS2307,后者演示bar: getBar忘记调用导致的 TS2345 类型不兼容),配合 code/errors/tsconfig.json(noEmit: true纯类型检查)即可在本地亲手触发并观察这些错误,从而加深理解。
小结
本文覆盖了 docs/errors/common-errors.md 中全部五类高频错误:TS2304(全局名字未声明)、TS2307(模块缺声明)、TS1148(模块缺编译格式)、catch 子句变量类型标注、以及React 声明文件重复。它们的共同底层逻辑是:TypeScript 的"声明优先"设计——无论是全局变量还是模块,凡是运行时存在的东西,都需要通过环境声明显式告知编译器;凡是模块输出,都需要明确编译格式。掌握这一逻辑,再配合 docs/errors/interpreting-errors.md 的因果链解读法,你就能在 IDE 中快速定位并修复绝大多数日常类型错误。
- 教程
【免费下载链接】typescript-book
:books: The definitive guide to TypeScript and possibly the best TypeScript book :book:. Free and Open Source 🌹
相关推荐
TypeScript 错误全解析:读懂编译器报错信息与高频常见错误修复指南(TypeScript Deep Dive)
TypeScript 错误全解析:读懂编译器报错信息与高频常见错误修复指南(TypeScript Deep Dive) TypeScript 是一门以"开发者帮
教程如何解决Immersive Translate沉浸式翻译的7个典型问题
如何解决Immersive Translate沉浸式翻译的7个典型问题 Immersive Translate沉浸式翻译是一款功能强大的双语网页翻译扩展,能够实
前端AI 应用Sim 项目 React Query 最佳实践审计指南:Key Factory、staleTime、Mutation 与服务器状态所有权
Sim 项目 React Query 最佳实践审计指南:Key Factory、staleTime、Mutation 与服务器状态所有权 本指南基于 Sim 仓
教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考