【问题标题】:Ensure an interface is in par with a union type?确保接口与联合类型相同?
【发布时间】:2020-12-07 18:21:55
【问题描述】:

鉴于此联合类型:

type Type = 'one' | 'two' | 'three'

我如何键入以下对象以确保它a) 涵盖Type 的所有可能值; b) 允许我拥有每种类型的函数签名?

const factories = {
    one(a: string) { return /* whatever */ },
    two(a: number, b: number) { return /* whatever */ },
    three() { return /* whatever */ }
}

function getFactory<T extends keyof typeof factories>(type: T): typeof factories[T] {
    return factories[type]
}

如果我没有像上面那样键入对象,我会进行完整的类型检查,但不是详尽无遗 - 我很容易忘记或拼错成员。在我的实际用例中,Type 有 30 多种可能性并且还在不断增长,所以这真的很重要。

如果我使用Record,例如const factories: Record&lt;Type, any&gt;const factories: Record&lt;Type, Function&gt;,它会详尽无遗,但我会放松对函数签名的类型检查。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    如果您只是希望 typescript 在缺少键时抛出错误,那么只需在声明后添加一个强制转换:

    const factories = {
        one(a: string) { return /* whatever */ },
        two(a: number, b: number) { return /* whatever */ },
        three() { return /* whatever */ }
    };
    
    factories as Record<Type, any>;
    

    如果您将three 更改为foo,则转换将出错,指出存在不兼容的错误类型。您也可以使用强制 typescript 将键类型缩小到 Type 的函数:

    type Type = 'one' | 'two' | 'three';
    
    function forceTypeKey<T>(obj: Record<Type, any> & T): T {
        return obj;
    }
    
    const factories = forceTypeKey({
        one(a: string) { return /* whatever */ },
        two(a: number, b: number) { return /* whatever */ },
        three() { return /* whatever */ }
    });
    

    【讨论】:

    • 聪明。我更喜欢一个不意味着额外代码生成的解决方案,但如果我在接下来的几天内没有得到一个,我会接受你的回答。谢谢。
    【解决方案2】:

    @Aplet123 的answer 是正确的;我只是想跟进一个不会对发出的 JavaScript 代码造成任何更改的版本。就我个人而言,我不认为多写一两行 JavaScript 有什么大不了的,但是如果你想将类型检查的效果完全限制在静态类型系统中,你可以这样做:

    type ExhaustiveFactories<T extends Record<Type, any> =
      typeof factories> = void;
    

    发出 JavaScript 时,该行将被完全删除。如果它可以编译,那是因为factoriesType 的每个元素都有一个属性;否则,您将收到一条错误消息,告诉您Type 的哪些属性或哪些属性在factories 中丢失,例如:

    type Type = 'one' | 'two' | 'three';
    
    const factories = {
      one(a: string) { return /* whatever */ },
      two(a: number, b: number) { return /* whatever */ },
    }
    
    type ExhaustiveFactories<T extends Record<Type, any> =
      typeof factories> = void; // error!
    //~~~~~~~~~~~~~~~~ <-- Property "three" is missing
    

    或:

    const factories = {
      one(a: string) { return /* whatever */ },
      two(a: number, b: number) { return /* whatever */ },
      thwee() { return /* whatever */ },
    }
    
    type ExhaustiveFactories<T extends Record<Type, any> =
      typeof factories> = void; // error!
    //~~~~~~~~~~~~~~~~ <-- Property "three" is missing
    

    Playground link to code

    【讨论】:

    • 正是我想要的,谢谢!我刚刚用Function 替换了any,因为我真的希望所有条目都是……功能。另外,我必须指出,这并不妨碍我添加 Type 上不存在的属性 - 但 this 没什么大不了的 ;)
    【解决方案3】:
    type Type = "one" | "two" | "three";
    
    type Factories = {
      [K in Type]: typeof factories[K];
    };
    
    // It will become circular so you can't assign here
    const factories = {
      one(a: string) {
        return; /* whatever */
      },
      two(a: number, b: number) {
        return; /* whatever */
      },
      three() {
        return; /* whatever */
      }
    };
    
    // But you will get an error due to lack of support
    // for index type if your const factories is missing
    // a key from type 
    function getFactory<T extends Type>(type: T): Factories[T] {
      return factories[type];
    }
    
    const f = getFactory("one"); // will be (a: string) => void
    

    【讨论】:

    • 没有。正如我在问题中所说,这与使用Record 相同。使用这个解决方案,我放松了对不同工厂的类型检查,即。 getFactory() 总是返回 (...args: any[]) =&gt; any
    • 已更新。希望这对您有所帮助。
    • 好多了,好多了,按要求工作-谢谢。但是,我仍然更喜欢 @jcalz 的答案,因为 Intellisense 然后会告诉我缺少哪个属性键。
    【解决方案4】:

    这是对jcalz's solution 的补充,它还检查多余的属性(请参阅您的comment):

    type AssertAssignable<Expected, Actual extends Expected> = Actual
    
    type AssertFactories =
      AssertAssignable<Record<Type, any>, typeof factories> &
      AssertAssignable<typeof factories, Record<Type, any>>
    
    // Example
    const factories = {
      one(a: string) { return ... },
      two(a: number, b: number) { return ... },
      three: "foo",
      four: 42  // <-- error
    }
    

    更多concise magic 替代方案:

    type AssertFactories2<U extends (
      (<T>() => T extends typeof factories ? 1 : 2) extends
      (<T>() => T extends {[P in Type]: typeof factories[P] } ? 1 : 2) ? typeof factories : 
      "Sorry, excess or missing property in typeof factories"
      )
      = typeof factories> = U
    

    这种看起来很复杂的条件类型只是确保typeof factories{[P in Type]: typeof factories[P] }相同

    你可以看看playground

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-11
      • 2018-10-01
      • 2017-09-16
      相关资源
      最近更新 更多