[declare global] 全局范围的扩大仅可直接嵌套在外部模块中或环境模块声明中

1,092 阅读3分钟

问题引入

写过ts的小伙伴应该都遇到过这个问题:

image.png

编辑器报错:Augmentations for the global scope can only be directly nested in external modules or ambient module declarations。 翻译成中文是:全局范围的扩大仅可直接嵌套在外部模块中或环境模块声明中

网上很容易找到解决方法:

image.png

But Why?这里的外部模块和环境模块声明又是什么呢?

模块

模块的概念很直接,与nodejs中一样,一个文件就是一个广义上的模块,不过对ts而言,只有ts文件才是一个模块。

ts模块有两种类型:全局模块(环境模块,ambient module)和文件模块(外部模块,external modules)。

怎么区分呢?默认情况下,一个ts文件是全局模块,模块中的类型或者变量声明,处于全局命名空间中,这种情况下,A文件中的变量在B文件中可以使用,因为它们都在全局空间中。

比如文件a.ts中:

// a.ts
const msgA = '这是变量'

type MSG = string

在文件b.ts中可以直接使用:

// b.ts
const msgB:MSG = '这是变量b'

console.log(msgA+msgB);

虽然很神奇,但有时候这种行为很危险。

文件模块:如果在ts文件中有import或者export,则它会在这个文件中创建一个本地作用域。这种情况下,如果B是一个外部模块,A文件想要使用B文件中的变量,则需要先导入。

declare 关键字

再来看ts中的declare关键字。

通过 declare 关键字来告诉 TypeScript,你正在试图表述一个其他地方已经存在的代码。它可以:

  • 声明 变量/函数/类/类型等
  • 声明模块
  • 声明命名空间
  • 声明global

问题详解

看这个代码:

// global.d.ts 文件
declare global {
    interface Window {
        act_name: string;
    }
}

我们的目的是:在全局命名空间中,声明Window对象上有一个 act_name 的属性。这里我们用了 declare global ,本来就是多此一举,因为 这个ts文件中没有使用import/export,它是一个全局模块!所以可以直接去掉 declare global,就能达到我们的目的:

// global.d.ts 文件
interface Window {
    act_name: string;
}

这是第一种解决方法,简单粗暴。第二种解决方法是:让全局范围的扩大嵌套在外部模块中:

// global.d.ts 文件
declare global {
    interface Window {
        act_name: string;
    }
}
export {}

还剩下一个东西没有弄清楚:全局范围的扩大仅可直接嵌套在环境模块声明中,这个又是什么意思呢? 这里有点歧义,"环境模块声明" 并不是指环境模块文件(全局模块文件),否则的话,下面这个声明就不会报错了:

// global.d.ts 文件
declare global {
    interface Window {
        act_name: string;
    }
}

这里的环境模块声明是指 declare module 语句,嵌套在环境模块声明中,即:

declare module 'aa.ts' {
    const a:string;
    global {
        interface Window {
            act_name: string;
        }
    }
}

上面这种也是合法的,表示 存在一个外部的文件 aa.ts,在其中 声明了全局范围的变量。

另外,除了环境模块声明,还有其他的环境声明:

// ambient module declaration
declare module "mymod" { /*... */ }

// ambient namespace declaration
declare namespace MyNamespace { /*... */ }

// ambient variable declaration
declare const myVar: string;

完结!