import导致.d.ts中的全局类型失效

2022/10/11 TypeScript

# 定义全局类型

  1. 新建user.d.ts文件,并把该文件加入tsconfig.json的include属性中,表明该文件加入tsc的编译。

  2. 编写类型定义

    // user.d.ts
    type UserName = 'Alice' | 'Bob'
    
    interface User {
      name: UserName;
      age: number;
    }
    
    1
    2
    3
    4
    5
    6
    7

    此时user.d.ts中定义的类型为全局类型,项目中其他地方直接可以使用:

    const user: User = { name: 'Alice', age: 20 };
    
    1

假设现在类型User中新增加一个address属性,且该属性的类型需要从其他文件引入

// address.ts
export const address = {
  北京: 'beijing',
  上海: 'shanghai',
  广州: 'guangzhou',
  深圳: 'shenzhen',
};
1
2
3
4
5
6
7
// user.d.ts
import { address } from './address'

type UserName = 'Alice' | 'Bob'

interface User {
  name: UserName;
  age: number;
  address: keyof typeof address
}
1
2
3
4
5
6
7
8
9
10

按照最初的使用方式:

// index.ts
const user: User = { name: 'Alice', age: 20, address: '上海' };
1
2

此时将会报错:

type User = /*unresolved*/ any
找不到名称“User”。
1
2

原因:TypeScript与ECMAScript 2015一样,任何包含顶级import或者export的文件都被当成一个模块。相反地,如果一个文件不带有顶级的import或者export声明,那么它的内容被视为全局可见的(因此对模块也是可见的),这就是为什么有时候没有引用某个.d.ts文件,但是在该.d.ts文件内部的类型定义在其它文件中仍然能检测得到,这是因为该.d.ts文件定义的类型已经变成全局的了。

# 解决方法

  1. 手动引入类型定义

    // index.ts
    import { User } from './user.d.ts';
    
    const user: User = { name: 'Alice', age: 20, address: '上海' };
    
    1
    2
    3
    4
  2. 使用declare global

    // user.d.ts
    import { address } from './address'
    
    declare global {
      type UserName = 'Alice' | 'Bob'
    
      interface User {
        name: UserName;
        age: number;
        address: keyof typeof address;
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    使用方式:

    const user: User = { name: 'Alice', age: 20, address: '上海' };
    
    1
  3. 作为命名空间全局导出

    // user.d.ts
    import { address } from './address'
    
    declare namespace UserType {
    
      type UserName = 'Alice' | 'Bob'
    
      interface User {
        name: UserName;
        age: number;
        address: keyof typeof address;
      }
    }
    
    export = UserType;
    export as namespace UserType;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    使用方式:

    // index.ts
    // 注意是在命名空间下导出的,所以引用方式是UserType.User
    const user: UserType.User = { name: 'Alice', age: 20, address: '上海' };
    
    1
    2
    3

上面的第三种解决方案,通过命名空间的全局导出方式,就是官方文档内的UMD模块 (opens new window)的说明:

有些模块被设计成兼容多个模块加载器,或者不使用模块加载器(全局变量)。它们以UMD模块为代表。这些库可以通过导入的形式或全局变量的形式访问。例如: math-lib.d.ts

export function isPrime(x: number): boolean;
export as namespace mathLib;
1
2

之后,这个库可以在某个模块里通过导入来使用:

import { isPrime } from "math-lib";
isPrime(2);
mathLib.isPrime(2); // 错误: 不能在模块内使用全局定义。
1
2
3

它同样可以通过全局变量的形式使用,但只能在某个脚本(指不带有模块导入或导出的脚本文件)里。

mathLib.isPrime(2);
1

参考资料

最近更新: 2025年03月13日 17:49:47