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

Typescript 在手写的d.ts文件中,如何公开模块根中一个名称空间中的函数?,typescript,typescript-declarations,typescript-namespace,Typescript,Typescript Declarations,Typescript Namespace,我正在开发一个repo,它全部使用javascript,但可以导出手写类型声明() 代码库的结构是,它有一个前端和一个后端,加上一个公共API,该API提供了自己的一些方便的函数,此外还可以直接从前端和后端重新导出一些函数 大概是这样的: declare module `foo` { // functions that only exist in the public API function a function b function c // functions ex

我正在开发一个repo,它全部使用javascript,但可以导出手写类型声明()

代码库的结构是,它有一个前端和一个后端,加上一个公共API,该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>
  }

  ...
}
声明模块“自动合并”{
...
函数getObjectById(doc:doc,objectId:OpId):doc
命名空间前端{
...
函数getObjectById(doc:doc,objectId:OpId):doc
}
...
}

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

一种可能是定义一个arrow函数类型别名,并在这两个位置使用它。例如:

声明模块“自动合并”{
键入GetObjectById=(doc:doc,objectId:OpId)=>doc
常量getObjectById:getObjectById
命名空间前端{
常量getObjectById:getObjectById
}
}
不幸的是,不可能对“常规”函数声明直接执行相同的操作(请参阅)

箭头函数和函数声明是非常重要的,尤其是围绕函数中的
this
的范围。例如,箭头函数不能有
参数:

//不允许
const fn=(this:SomeContext)=>void
//允许
函数fn(this:SomeConext):void

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

我认为您所寻找的名称空间是无法实现的。但是名称空间是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>;
  };

const前端:{
//在主要范围和前端
//用typeof重新声明
变更:变更类型;
emptyChange:emptyChange的类型;
from:from的类型;
getActorId:getActorId的类型;
getConflicts:getConflicts的类型;
getLastLocalChange:getLastLocalChange的类型;
getObjectById:getObjectById的类型;
getObjectId:getObjectId的类型;
init:init的类型;
//仅在前端
//从头开始申报
applyPatch(
博士:博士,
补丁:补丁,
后端状态?:后端状态
):Doc;
getBackendState(doc:doc):后端状态;
getElementId(列表:任意):字符串[];
setActorId(doc:doc,actorId:string):doc;
};
上述情况并不理想,因为您需要键入导出的函数名两次,这很容易出错,但对于您正在处理的类型数量来说,可能完全没有问题

另一个选项是使用辅助模块首先将相关功能组合在一起,然后从辅助模块重新导出,再从主模块重新导入:

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 */
}

声明模块“自动合并/前端”{
出口{
改变
空变,
从…起
赫塔克特里德,
获取冲突,
getLastLocalChange,
getObjectById,
getObjectId,
初始化
}从“自动合并”;
从“自动合并”导入{Doc,Patch,BackendState};
导出函数applyPatch(
博士:博士,
补丁:补丁,
后端状态?:后端状态
):Doc;
导出函数getBackendState(doc:doc):BackendState;
导出函数getElementId(列表:任意):字符串[];
导出函数setActorId(doc:doc,actorId:string):doc;
}
声明模块“自动合并”{
/*其他东西*/
从“自动合并/前端”导入*作为前端
const Frontend:typeof _Frontend
/*其他东西*/
}
由于进出口的循环性质,上述情况有点复杂,相当不雅。您可以尝试将所有相关函数移动到
模块“automerge/frontend”
,但是您需要从那里重新导出它们,这将稍微改变语义,并且所有导出都需要显式(前缀为
export
关键字-例如:
export type Doc=FreezeObject;


作为最正确、最经得起未来考验的解决方案,我建议将代码重构为没有任何循环依赖关系的模块——可能需要创建一个公共模块来对共享类型进行分组


顺便说一句。如果您对上述任何选项感兴趣,请让我知道,我很乐意创建一个公关,我们可以在那里进行讨论。

类似的内容将部分帮助您:

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

}
声明模块“自动合并”{
命名空间前端{
函数getObjectById(doc:T,objectId:any):T;
}
const getObjectById:typeof Frontend.getObjectById;
}

优点:

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

  • 并没有真正消除使用完全相同的名称声明const/函数两次的需要

这是一个简化的示例,但您可以通过以下方式实现无复制:

//backend.d.ts
将模块声明为“后端”{
导出函数减法(a:数字,b:数字):数字;
}
然后:

//foo.d.ts
声明模块“foo”{
导出功能添加(a:编号,b:编号):编号;
从“后端”导出*;
从“后端”将*导出为B;
}
最后是用法:

//main.ts
从“foo”导入*作为foo;
foo.Add(1,2);