微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何为具有大量私有类的功能模块组织 Typescript 类型定义?

如何解决如何为具有大量私有类的功能模块组织 Typescript 类型定义?

我正在为一个名为 fessonia 的库编写类型定义。我有一些这样做的经验,但这个库的组织方式与我使用过的其他库不同,我不知道如何处理它。

这个库的 index.js 很小:

const getFessonia = (opts = {}) => {
  require('./lib/util/config')(opts);
  const Fessonia = {
    FFmpegCommand: require('./lib/ffmpeg_command'),FFmpegInput: require('./lib/ffmpeg_input'),FFmpegOutput: require('./lib/ffmpeg_output'),FilterNode: require('./lib/filter_node'),FilterChain: require('./lib/filter_chain')
  };
  return Fessonia;
}

module.exports = getFessonia;

它导出一个函数,该函数返回一个对象,其中的每个成员都是一个类。 (到目前为止,我在 lib 中遇到的每个文件都使用认导出。)我从 module function template 开始,但我正在努力在我的一些指导原则之间找到和谐:

  • 类型定义应促进使用此库的最佳实践。我的意思是我不会费心为标记@private标记getFessonia方法创建定义否则不打算/推荐用于外部使用。根据库文档,FFmpegCommand 是此库的唯一公共接口。虽然没有什么可以阻止开发人员直接导入 getFessonia,但不应该(因为,例如,本应在 import getFessonia from '@tedconf/fessonia'; // note the type assignment const config: getFessonia.ConfigOpts = { debug: true,}; const { FFmpegCommand,FFmpegInput,FFmpegOutput } = getFessonia(config); 中设置的配置尚未设置,并且可能会导致错误).
  • 类型定义应该很有用。下游开发人员应该能够将类型分配给他们的变量à la:
.d.ts

到目前为止,我采取的方法是为创建有用类型定义所需的每个 .js 文件创建 index.d.ts 文件,然后将它们导入 getFessonia 并根据需要重新导出在 opts 命名空间中。例如,为了为 lib/util/config 参数提供类型定义,我需要读取 getConfig,它具有认导出 import getLogger from './logger'; export = getConfig; /** * Get the config object,optionally updated with new options */ declare function getConfig(options?: Partial<getConfig.Config>): getConfig.Config; declare namespace getConfig { export interface Config { ffmpeg_bin: string; ffprobe_bin: string; debug: boolean; log_warnings: boolean; logger: getLogger.Logger; } } 。它的类型文件最终看起来像这样:

index.d.ts

... 我在 import getConfig from './lib/util/config'; export = getFessonia; /** * Main function interface to the library. Returns object of classes when called. */ declare function getFessonia(opts?: Partial<getFessonia.ConfigOpts>): getFessonia.Fessonia; declare namespace getFessonia { export interface Fessonia { // Todo FFmpegCommand: any; FFmpegInput: any; FFmpegOutput: any; FilterNode: any; FilterChain: any; } // note I've just aliased and re-exported this export type ConfigOpts = Partial<getConfig.Config>; } 中这样使用它:

getConfig

我认为我可能走错路的原因:

  • 我认为我不需要函数 lib/util/config 的定义,特别是因为我不想宣传它的直接用法Config 具有认导出是否重要?我应该直接导出 index.d.ts 接口并从 Config 重新导出吗?或者我可能会删除函数定义并将 getConfig 接口保留在命名空间下;这样,如果 getFessonia 将来成为公共函数,我只需添加函数的定义即可。
  • getFessonia 命名空间下重新导出既乏味又不是特别优雅。
  • 我可能会在 FFmpegOutput 下进行大量嵌套(和别名)。例如,FFmpegOption 的构造函数接受一个参数,它实际上只是内部类 import getFessonia from '@tedconf/fessonia'; const { FFmpegCommand,FFmpegOutput } = getFessonia(); // note the deep nesting const outputoptions: getFessonia.FFmpeg.Output.Options = { /* some stuff */ }; const output = new FFmpegOutput('some/path',outputoptions); 的参数映射,因此下游代码可能最终看起来像:
getFessonia
  • FFmpegOutput 的参数和 FFmpeg 的形状定义为同级不是很直观。
  • 出于组织/命名冲突避免的原因,我正在创建 <div class="tags"> {posts.map(post => [...new Set(post.frontmatter.tags)].map(tag => ( <Link key={tag + `tag`} to={`/tags/${kebabCase(tag)}/`} className="tag is light" > {tag} </Link> )) )} </div> 命名空间。

你坚持到了最后!感谢您阅读到这里。虽然我怀疑没有一个“正确”的答案,但我期待阅读其他人采用的方法,并且很高兴被指出我可以通过示例学习的文章或相关代码存储库。谢谢!

解决方法

@alex-wayne 的评论帮助我重置了大脑。谢谢。

出于某种原因,我在编写类型定义时好像库使用默认导出意味着我不能从我的 .d.ts 文件中导出其他内容。可能是睡眠不足!

无论如何,除了默认导出函数 getFessonia 之外,我最终导出了一个接口 Fessonia 来描述返回值以及同名的命名空间 (more on TypeScript's combining behavior)为 getFessonia 的选项以及库提供的各种其他实体提供类型。 index.d.ts 最终看起来像:

import { FessoniaConfig } from './lib/util/config';
import FFmpegCommand from './lib/ffmpeg_command';
import FFmpegInput from './lib/ffmpeg_input';
import FFmpegOutput from './lib/ffmpeg_output';
import FilterChain from './lib/filter_chain';
import FilterNode from './lib/filter_node';

/** Main function interface to the library. Returns object of classes when called. */
export default function getFessonia(opts?: Partial<Fessonia.ConfigOpts>): Fessonia;

export interface Fessonia {
  FFmpegCommand: typeof FFmpegCommand;
  FFmpegInput: typeof FFmpegInput;
  FFmpegOutput: typeof FFmpegOutput;
  FilterChain: typeof FilterChain;
  FilterNode: typeof FilterNode;
}

// re-export only types (i.e.,not constructors) to prevent direct instantiation
import type FFmpegCommandType from './lib/ffmpeg_command';
import type FFmpegError from './lib/ffmpeg_error';
import type FFmpegInputType from './lib/ffmpeg_input';
import type FFmpegOutputType from './lib/ffmpeg_output';
import type FilterNodeType from './lib/filter_node';
import type FilterChainType from './lib/filter_chain';
export namespace Fessonia {
    export type ConfigOpts = Partial<FessoniaConfig>;

    export {
      FFmpegCommandType as FFmpegCommand,FFmpegError,FFmpegInputType as FFmpegInput,FFmpegOutputType as FFmpegOutput,FilterChainType as FilterChain,FilterNodeType as FilterNode,};
}

对于属于 Fessonia 对象的类,我的一般方法是为每个类创建一个类型定义(忽略私有成员)并将其导出。如果类函数具有复杂类型的参数,我会为这些参数创建定义并将它们导出到与类同名的命名空间中,例如:

// abridged version of types/lib/ffmpeg_input.d.ts
export default FFmpegInput;

declare class FFmpegInput {
    constructor(url: FFmpegInput.UrlParam,options?: FFmpegInput.Options);
}

declare namespace FFmpegInput {
    export type Options = Map<string,FFmpegOption.OptionValue> | { [key: string]: FFmpegOption.OptionValue };
    export type UrlParam = string | FilterNode | FilterChain | FilterGraph;
}

由于重新导出了 index.d.ts 底部的类型,这个下游代码成为可能:

import getFessonia,{ Fessonia } from '@tedconf/fessonia';

const { FFmpegCommand,FFmpegInput,FFmpegOutput } = getFessonia();
// note the type assignment
const outputOptions: Fessonia.FFmpegOutput.Options = { /* some stuff */ };
const output = new FFmpegOutput('some/path',outputOptions);
const cmd = new FFmpegCommand(commandOpts);

虽然这与我原来的没有太大的不同,但感觉确实有所改进。我不必发明太多新的组织结构;类型名称与代码库的结构一致(添加了 Fessonia 命名空间)。它是可读的。

我第一次输入这个库是 available on GitHub

感谢所有评论并让我产生不同想法的人。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。