编程前端开发者

TypeScript 中导入的 JavaScript 模块的类型机制是如何工作的?如果源 JS 文件不包含类型,如何对导入进行类型定义?使用 any 导入的细节和风险是什么?

用 Hintsage AI 助手通过面试

答案。

问题历史:

TypeScript 旨在为现有的 JS 应用程序添加静态类型。问题是——如果我们导入一个用纯 JavaScript 编写的外部或自定义模块,而没有类型,应该如何进行类型定义?在这种情况下,TypeScript 会使用所谓的声明文件(.d.ts),或者自动推断类型(有时会错误)。

问题:

如果 TypeScript 在导入时找不到合适的类型描述,变量将获得类型 any,这意味着类型安全的完全丧失。这可能导致编译器无法察觉的错误,以及运行时的 bug。开发人员经常忘记明确声明导入的函数/对象的类型。

解决方案:

  1. 对于自己的 JS 模块,可以手动编写类型声明(.d.ts 文件)。
  2. 对于流行的库,通常会有 @types/ 包。
  3. 可以在导入时显式声明类型,自行描述所需对象的结构。
  4. 建议避免使用 any,若无法避免,则应尽量限制使用范围。

代码示例:

// 1. JS 模块导入的显式类型 import myFunc from './myLib'; declare function myFunc(x: number): boolean; // 2. 从 JS 模块导入,已创建 myLib.d.ts 文件,内容为 export function myFunc(x: number): boolean; import { myFunc } from './myLib'; // 3. 导入未类型化的模块并明确描述类型 import * as legacy from './legacy'; const typedLegacy: { runTask: (name: string) => void } = legacy;

关键特性:

  • 在没有声明的情况下,默认导入的值获得类型 any,这违反了类型安全
  • 最佳做法是为外部模块/自定义库创建 .d.ts 文件
  • 如果需要,可能会通过 declare/接口本地声明外部函数/模块

反向问题。

TypeScript 可以在没有声明的情况下自动推断导入的 JS 模块的类型吗?

不能,如果文件是用 JavaScript 编写的,并且没有类型声明,TypeScript 被迫假定其为 any,且除简单情况外,失去类型信息(export const x = 1;)。

如果在 JS 模块中出现新字段,是否可以“扩展”导入的类型?

只能在更新声明文件(.d.ts)后。如果类型在 .d.ts 中被固定,TypeScript 将把它们视为“真实”,任何新字段将保持未类型化,或者导致错误。

在 TypeScript 项目中导入没有 @types/ 和声明的外部 JS 模块安全吗?

不,这会大幅降低安全性——整个导入工作变得未类型化(any),编译器不会报错,即使模块不可用或者 API 已更改。处理这些模块只应作为临时解决方案,伴随明确的数据类型或代码隔离。

常见错误和反模式

  • 忘记为外部包编写声明(.d.ts)
  • 在导入 JS 模块时盲目信任隐式 any
  • 违反类型安全代码的边界

实际案例

消极案例

开发人员在没有声明的情况下导入外部 JS 库,自信地使用 API,得到类型 any。库更新后,方法签名改变,但没有发生错误,仅在运行时出现 bug。

优点:

  • 快速简单地连接任何 JS 模块

缺点:

  • 不保证类型安全,错误在运行前无法捕获

积极案例

创建 .d.ts 声明文件或添加 @types/ 包,API 描述严格对应于源 JS 模块。所有导入的方法都有类型,IDE 提供自动补全,任何不匹配都会显示编译错误。

优点:

  • 类型安全,运行前警告错误
  • 代码中的自动补全和文档支持

缺点:

  • 需要时间编写 .d.ts,维护 JS 模块更新时的类型