如何在同一命名空间中的多个文件上合并打字稿接口

How can I merge typescript interfaces over multiple files in the same namespace

我正在尝试重构一个笨重的配置 interface/object,方法是将它的各个部分分成单独的文件,放在 namespace 我巧妙地命名为 Config.

文档中提到 namespaces that span multiple files and declaration merging of interfaces,但我似乎无法让它们协同工作。

src/config/index.ts

/// <reference path="./server.ts" />
import fs from 'fs';
import json5 from 'json5';

const _config = readConfig();

namespace Config {
    export const config = _config;

    export interface IConfig {
        someGeneralProperty: {
            // ...
        }
    }
}

function readConfig(): Config.IConfig {
    return json5.parse(fs.readFileSync('./path/to/config.json', 'utf-8'));
}

function doSomeOtherStuff() {
    // fails: Property 'server' does not exist on type 'IConfig'.
    console.log(_config.server.host);
}

src/config/server.ts

/// <reference path="./index.ts" />

namespace Config {
    export interface IConfig {
        server: {
            host: string;
            port: number;
        }
    }
}

src/index.ts

// fails: Module '"./config"' has no exported member 'config'.
import { config } from './config'; 

// fails: Cannot use namespace 'Config' as a value.
// fails: Namespace 'Config' has no exported member 'config'.
import config = Config.config;

我尝试了多种导出方式,例如 export default Config;export namespace Config {...} 在每个 src/config/... 文件中,将 export const config 更改为 export var config.在 src/config/index.ts 中,我尝试了 export * from './server'。似乎没有任何帮助。

我有一种感觉,我正在做这一切都是错误的。

奇怪的是,每个文件中命名空间内的接口都是从命名空间导出的,所以在src/index.ts中,我可以这样做:

import IConfig = Config.IConfig;

let c: IConfig;
console.log(c.server.host);

但我无法在 src/config/index.tssrc/config/server.ts 中做到这一点。

首先你应该自己决定,如果你想将 config 对象分配给模块范围(即 import/export)或全局范围(即window 在浏览器中,global 在节点中)。

命名空间的主要目的是在全局范围内定义properties/values。正如您在链接中正确指出的那样,合并了同名命名空间 - 包括包含的内部成员,如 IConfig 接口。

这是一笔交易:只有当包含 namespace 的文件是脚本(顶级没有 import/export 的非模块文件)时才会发生合并。

src/config/index.ts 中,您有 import 条语句,因此文件成为一个模块并且 namespace Config 不会被合并。相反,它是一个模块内部命名空间,甚至没有 exported(参见文档中的 Needless Namespacing, Do not use namespaces in modules)。 src/config/server.ts 中的 Config 命名空间形成了自己的全局命名空间(非模块文件),这就是为什么您仍然可以使用包含的 IConfig 类型。

总而言之,如果您想要全局配置(值和类型),请确保多文件部分命名空间的每个部分都在非模块文件中声明。如果要从模块导出配置(如果可行,首选方式!;更好的封装,没有全局范围污染,"modern" 方式),请继续阅读。

备选方案:在模块中导出配置

src/config/server.ts:

export interface ServerConfig {
  server: {
    host: string;
    port: number;
  }
}

// you could also read a server-specific config value here, export it
// and merge it with a separately read common config value in index.ts
// export serverConfig: ServerConfig = readServerConfig()

src/config/index.ts:

import { ServerConfig } from "./server"

interface CommonConfig {
  someGeneralProperty: {
    // ...
  }
}

export type IConfig = CommonConfig & ServerConfig

export const config: IConfig = readConfig(); // whatever readConfig looks like

src/index.ts:

import { config } from './config'; 

config.server;
config.someGeneralProperty

随意调整您需要的部分。希望对您有帮助。