我有一些扩展:

type ExtensionOptions = { name: string }

type BoldOptions = ExtensionOptions & { boldShortcut?: string }
type ItalicOptions = ExtensionOptions & { italicShortcut?: string }
type LinkOptions = ExtensionOptions & { autoLink?: boolean }

declare class Extension {
    constructor(options: ExtensionOptions)
}

declare class BoldExtension extends Extension {
  isSelectionBold: boolean
  constructor(options: BoldOptions)
}

declare class ItalicExtension extends Extension {
  isSelectionItalic: boolean
  constructor(options: ItalicOptions)
}

declare class LinkExtension extends Extension {
  isSelectionLink: boolean
  constructor(options: LinkOptions)
}

我想创建一个数组类型,这样我就可以传递带有扩展的元组和它的带有智能感知的选项.例如:

// Generic type. It works but without intellisense
type Extensions = Array<[Extension, ExtensionOptions]>

const extensions = [
  [BoldExtension, { name: 'italic' }],
  [ItalicExtension, { name: 'italic' }],
  [LinkExtension, { name: 'link', autoLink: true }] // Need intellisense for LinkExtension here
]

我试着做了一些实验:

type Extensions<T> = Array<[
  T,
  T extends new (...args: infer Options) => any
    ? Options[0] // we know that first argument is the `options`
    : never
]>

如果你想看一下,这是TypeScript playground link元.

推荐答案

Typescript 中没有(简单的)特定的ExtensionPair类型来满足您的需求.它应该是两个元素的tuple type,其中第一个元素是产生some Extension实例的一个参数construct signature,而第二个元素是其参数的类型.这两个元素之间的联系需要generics来表达.如果TypeScrip有existentially quantified generics(如microsoft/TypeScript#14466中所要求的),那么您可以用一种使ExtensionPair成为特定类型的方式来表达该"一些".但它不是,所以我们需要使它成为一个常规的泛型(universally量化,意思是"全部"而不是"一些"):

type ExtensionPair<T extends new (arg: any) => Extension> = 
  [T, ConstructorParameters<T>[0]];

如果有arraytuple个类型参数,则需要有相应的类型参数数组或元组.您可以将其写为mapped array/tuple type(泛型元组/数组类型的键上的映射类型将只迭代类数字索引,并将生成另一个元组/数组类型;请参阅前面的链接):

type ExtensionPairs<T extends (new (arg: any) => Extension)[]> = 
  { [I in keyof T]: ExtensionPair<T[I]> }

写出这样的类型可能很烦人,因为您将被迫指定一些冗余信息:

const extensions: ExtensionPairs<[
  typeof BoldExtension,
  typeof ItalicExtension,
  typeof LinkExtension
]> = [
    [BoldExtension, { name: 'italic' }],
    [ItalicExtension, { name: "" }],
    [LinkExtension, { name: "a", autoLink: true }]
  ];

如果编译器只有infer个这样的类型就好了,但是TypeScrip没有直接的能力(参见Typescript generics, infer object property type without a function call).相反,您可以编写一个泛型帮助器函数,因为类型脚本can会推断泛型函数调用的类型参数:

function asExtensionPairs<T extends (new (arg: any) => Extension)[]>(
  arr: [...ExtensionPairs<T>]) {
  return arr;
}

并使用它:

const extensions = asExtensionPairs([
  [BoldExtension, { name: 'italic' }],
  [ItalicExtension, { name: "" }],
  [LinkExtension, { name: "a", autoLink: true }]
]);
/* const extensions: [
    ExtensionPair<typeof BoldExtension>, 
    ExtensionPair<typeof ItalicExtension>, 
    ExtensionPair<typeof LinkExtension>
  ] */

这将为相同的类型(显示略有不同,但类型相同)生成相同的值,而不需要您自己将其写出类型.

它还有一个优势,可以从部分输入中提供自动建议/智能感知,并在您出错时提供错误:

const oops = asExtensionPairs([
  [BoldExtension, { name: "a", autoLink: true }], // error, 
  // autoLink is an excess property
])

Playground link to code

Typescript相关问答推荐

带有联合参数的静态大小数组

如何在函数参数中使用(或模仿)`success`的行为?

如何在第一次加载组件时加载ref数组

为什么从数组中删除一个值会删除打字错误?

为什么tsx不判断名称中有"—"的属性?'

泛型类型,引用其具有不同类型的属性之一

有没有可能产生输出T,它在视觉上省略了T中的一些键?

vue-tsc失败,错误引用项目可能无法禁用vue项目上的emit

如何从MAT-FORM-FIELD-MAT-SELECT中移除焦点?

如何将对象数组中的键映射到另一种对象类型的键?

如何避免多个类型参数重复?

类型判断数组或对象的isEmpty

有没有更干净的方法来更新**key of T**的值?

通过辅助函数获取嵌套属性时保留类型

JSX.Element';不可分配给类型';ReactNode';React功能HOC

定义一个只允许来自另一个类型的特定字符串文字的类型的最佳方式

为什么我无法在 TypeScript 中重新分配函数?

将 Angular 从 14 升级到 16 后出现环境变量注入问题

是否有可能不允许在 TypeScript 中设置类属性,但也会抛出运行时错误?

如何编写一个接受 (string|number|null|undefined) 但只返回 string 且可能返回 null|undefined 的函数?