繁体   English   中英

在手写的 d.ts 文件中,如何从模块根目录中的一个命名空间公开函数?

[英]In a handwritten d.ts file, how can I expose functions from one namespace in the module root?

我正在开发一个全部在 javascript 中但导出手写类型声明( automerge/index.d.ts )的存储库。

代码库的结构是它有一个前端和一个后端,加上一个公共的API,它提供了自己的一些便利功能,此外还直接从前端和后端重新导出了一些功能。

像这样的东西:

declare module `foo` {

  // functions that only exist in the public API
  function a
  function b
  function c

  // functions exposed directly from namespace A
  function q
  function r
  function s

  // functions exposed directly from namespace B
  function x
  function y
  function z

  namespace A {
    function q
    function r
    function s
    function t
  }

  namespace B {
    function v
    function w
    function x
    function y
    function z
  }

}

这是实际代码的摘录,显示了我们当前如何为重新导出的函数编写重复声明。

declare module 'automerge' {
  ...

  function getObjectById<T>(doc: Doc<T>, objectId: OpId): Doc<T>
  
  namespace Frontend {
    ...

    function getObjectById<T>(doc: Doc<T>, objectId: OpId): Doc<T>
  }

  ...
}

有没有办法避免两次编写这些声明?

我不认为你正在寻找的是可以通过命名空间实现的。 但是命名空间是 Typescript 早期的遗留功能,并且(强烈)不鼓励使用它们 - 来自官方文档

[...] 我们推荐在现代代码中使用模块而不是名称空间。

不久又一次:

因此,对于新项目,模块将是推荐的代码组织机制。

在提供类型定义的情况下,删除命名空间的使用应该相对简单。

最简单的选择是通过直接声明它们的类型来声明导出的对象。 Frontend的情况下,它看起来像这样:

const Frontend: {
    // in the main scope & Frontend
    // redeclared with typeof
    change: typeof change;
    emptyChange: typeof emptyChange;
    from: typeof from;
    getActorId: typeof getActorId;
    getConflicts: typeof getConflicts;
    getLastLocalChange: typeof getLastLocalChange;
    getObjectById: typeof getObjectById;
    getObjectId: typeof getObjectId;
    init: typeof init;

    // in Frontend only
    // declaration from scratch
    applyPatch<T>(
      doc: Doc<T>,
      patch: Patch,
      backendState?: BackendState
    ): Doc<T>;
    getBackendState<T>(doc: Doc<T>): BackendState;
    getElementIds(list: any): string[];
    setActorId<T>(doc: Doc<T>, actorId: string): Doc<T>;
  };

以上内容并不理想,因为您需要输入两次导出的 function 名称,这容易出错,但对于您处理的类型数量可能完全没问题。

另一种选择是使用辅助模块首先将相关的 function 组合在一起,然后从辅助模块重新导出它们并从主模块重新导入:

declare module "automerge/frontend" {
  export {
    change,
    emptyChange,
    from,
    getActorId,
    getConflicts,
    getLastLocalChange,
    getObjectById,
    getObjectId,
    init
  } from "automerge";
  import { Doc, Patch, BackendState } from "automerge";

  export function applyPatch<T>(
    doc: Doc<T>,
    patch: Patch,
    backendState?: BackendState
  ): Doc<T>;
  export function getBackendState<T>(doc: Doc<T>): BackendState;
  export function getElementIds(list: any): string[];
  export function setActorId<T>(doc: Doc<T>, actorId: string): Doc<T>;
}

declare module "automerge" {
  /* other stuff */
  import * as _Frontend from 'automerge/frontend'
  const Frontend: typeof _Frontend
  /* other stuff */
}

由于进出口的循环性质,上述内容有点令人费解且相当不雅。 您可以尝试将所有相关功能移动到module "automerge/frontend" ,但是您需要从那里重新导出它们,这会稍微改变语义并且所有导出都需要显式(以export关键字为前缀 - 例如: export type Doc<T> = FreezeObject<T>; )。


作为最正确和面向未来的解决方案,我可以建议将代码重构为没有任何循环依赖关系的模块——可能需要创建一个通用模块来对共享类型进行分组。

顺便提一句。 如果您对上述任何选项感兴趣,请告诉我,我很乐意创建一个 PR,我们可以在那里进行讨论。

这是简化的示例,但您可以通过这种方式实现不重复:

// backend.d.ts
declare module "backend" {
    export function Subtract(a: number, b: number): number;
}

然后:

// foo.d.ts
declare module "foo" {
    export function Add(a: number, b: number): number;
    export * from "backend";
    export * as B from "backend";
}

最后,用法:

// main.ts
import * as foo from "foo";

foo.Add(1, 2); // defined only in the "foo".
foo.Subtract(1, 2); // "backend" function exposed in the root of "foo".
foo.B.Subtract(1, 2); // same "backend" function exposed in the "B" (namespace) of "foo".

一种可能性是定义一个箭头 function 类型别名并在两个地方都使用它。 例如:

declare module "automerge" {
    type GetObjectById = <T>(doc: Doc<T>, objectId: OpId) => Doc<T>
    
    const getObjectById: GetObjectById

    namespace Frontend {
        const getObjectById: GetObjectById
    }
}

不幸的是,不可能直接对“常规” function 声明做同样的事情(见这里)。

箭头函数和 function 声明并不完全相同,尤其是围绕 function 中this范围。 例如箭头函数不能有this参数:

// not allowed
const fn = (this: SomeContext) => void
// allowed
function fn(this: SomeConext): void

但是,如果您不依赖它们不同的任何功能,或者可以在 js 代码中切换到箭头函数以确保安全,那么这应该可以工作。

这样的事情会部分帮助你:

declare module 'automerge' {
  
  namespace Frontend {
    function getObjectById<T>(doc: T, objectId: any): T;
  }
  
  const getObjectById: typeof Frontend.getObjectById;

}

在操场上试试

优点:

  • 通过重用已声明函数的类型来减少代码量。

缺点:

  • 并没有真正消除两次声明具有完全相同名称的 const/function 的需要。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM