首页 > 解决方案 > TypeScript 通用接口,仅具有使用特定模板参数值定义的属性

问题描述

我有一个通用接口,其中Textends boolean。如果Textends true,我希望一个属性存在,否则,我不希望它存在。这个接口扩展了另一个,所以我不能使用一个类型,我不想使用多个接口和一个联合类型。

我曾希望这never能解决我的难题。

interface MyInterface<T extends boolean> extends MyOtherInterface {
    someProp: string;
    conditionalProp: T extends true ? number : never;
}

const cTrue: MyInterface<true> = {
    firstProp: 1,
    someProp: 'foo',
    conditionalProp: 1
  },
  cFalse: MyInterface<false> = { // fails for missing conditionalProp: never
    firstProp: 2,
    someProp: 'bar'
  },
  cAnyTrue: MyInterface<any> = {
    firstProp: 3,
    someProp: 'baz',
    conditionalProp: 3
  },
  cAnyFalse: MyInterface<any> = { // fails for missing conditionalProp: never
    firstProp: 4,
    someProp: 'baz'
  };

我也尝试同时使用voidundefined。在所有三种情况下,当我实例化 时MyInterface<false>,它都需要设置conditionalProp。在第一种情况下,我可以将其分配为空,因为它的类型是never.

就像我之前暗示的那样,有一个冗长的解决方法。

interface MyInterfaceTrue extends MyOtherInterface {
    someProp: string;
    conditionalProp: number;
}

interface MyInterfaceFalse extends MyOtherInterface {
    someProp: string;
}

type MyInterface<T extends boolean> = T extends true ? MyInterfaceTrue : MyInterfaceFalse;

这是TypeScript Playground

标签: typescript

解决方案


解决方法

// The definition of the type with all the properties
interface _Data {
  always: number;
  sometimes: string;
}

// Conditionally exclude the properties if "true" or "false" was passed.
type Data<T extends boolean> = T extends true ? Omit<_Data, "sometimes"> : _Data;

/*
 * Tests
 */

const forTrue: Data<true> = {
  always: 0,
  // sometimes: "" => Error
};

const forFalse: Data<false> = {
  always: 0,
  sometimes: ""
}

此解决方案使用 typedef,但您几乎可以互换使用类型和接口。


让事情变得更好

为了使这整个事情更好地使用,我建议将“有条件地提取属性”的过程提取到可以重用的单独类型中。

type ConditionallyOmit<Condition extends boolean, T, K extends keyof T> = Condition extends true ? Omit<T, K> : T;

然后你可以像这样使用它:

type Data<T extends boolean> = ConditionallyOmit<T, _Data, "sometimes">

您当然可以内联原始定义:

type Data<T extends boolean> = ConditionallyOmit<T, {
  always: number;
  sometimes: string;
}, "sometimes">

这会让它变得尽可能干燥


为什么你需要另一种类型。

我很确定您目前无法仅使用一个界面来执行此操作。

我正在考虑通过扩展一个有条件地包含一个类型的类型来解决这个问题,或者{}- 类似于以下内容:

type TrueOrEmpty<Condition extends boolean, T> = Condition extends true ? {} : T;

这将为您提供Tor {},因此它会有条件地添加{}不会执行任何操作的或者包含您希望在其中包含的属性 - 取决于您传入的内容。

然后我会用它来扩展接口,比如:

interface Data<T extends boolean>
  // Properties you only want when you pass true
  extends TrueOrEmpty<T, { sometimes: string }>
{
    // Properties you always want to have
    always: number
}

但这会给您以下错误:

An interface can only extend an object type or intersection of object types with statically known members.

并且没有办法解决这个错误:Typescript 不知道静态包含什么,所以它不允许它。


推荐阅读