【问题标题】:Why won't TS let me index into a compatible object type?为什么 TS 不让我索引到兼容的对象类型?
【发布时间】:2019-06-17 22:57:31
【问题描述】:

我有以下类型定义。

type Key<Row> = {
  [P in keyof Row]: Row[P] extends string ? P : never
}[keyof Row];

type ID<Row> = Row[Key<Row>];

type Selected<Row> = {
  selected: boolean;
} & Row;

Key&lt;Row&gt; 返回Row 的所有属性键,其值为字符串。 See this answer 了解它的构造方式。

ID&lt;Row&gt;Row 的所有字符串值的并集。它始终是string,但如果字符串值是字符串常量,则可以很好地缩小范围,例如在

interface X {
  prop: "abc";
}

ID&lt;X&gt; 始终是"abc"

Selected&lt;Row&gt;Row 加上属性 selected: boolean 的交集类型。

以上都定义好了,我不明白为什么下面的函数定义会抛出错误:

const getId = <Row>(row: Selected<Row>, key: Key<Row>): ID<Selected<Row>> =>
  row[key];

TS 给我的类型错误粘贴在下面,但我不知道为什么 TS 不喜欢我的代码。

据我所见,row[key] 应该始终有效,因为即使 rowSelected&lt;Row&gt;Key&lt;Row&gt; 只包含一组更窄的键,那么键入具有更多属性的对象有什么问题我们实际使用的密钥?

Here is a link to the TS playground with this code showing the error


Type 'Selected<Row>[{ [P in keyof Row]: Row[P] extends string ? P : never; }[keyof Row]]' is not assignable to type '{ selected: boolean; }[(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)] & Row[(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<...>["selected"] extends string ? "selected" : never)]'.
  Type 'Selected<Row>[Row[keyof Row] extends string ? keyof Row : never]' is not assignable to type '{ selected: boolean; }[(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)] & Row[(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<...>["selected"] extends string ? "selected" : never)]'.
    Type 'Selected<Row>[keyof Row]' is not assignable to type '{ selected: boolean; }[(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)] & Row[(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<...>["selected"] extends string ? "selected" : never)]'.
      Type 'Row[string] | Row[number] | Row[symbol]' is not assignable to type '{ selected: boolean; }[(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)] & Row[(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<...>["selected"] extends string ? "selected" : never)]'.
        Type 'Row[string]' is not assignable to type '{ selected: boolean; }[(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)] & Row[(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<...>["selected"] extends string ? "selected" : never)]'.
          Type 'Row[string]' is not assignable to type '{ selected: boolean; }[(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)]'.
            Type 'Row' is not assignable to type '{ selected: boolean; }'.
              Type 'Row[string]' is not assignable to type 'boolean'.
                Type 'Selected<Row>[keyof Row]' is not assignable to type '{ selected: boolean; }[(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)]'.
                  Type 'keyof Row' is not assignable to type '(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)'.
                    Type 'keyof Row' is not assignable to type 'Selected<Row>[keyof Row] extends string ? keyof Row : never'.
                      Type 'Selected<Row>[keyof Row]' is not assignable to type 'boolean'.
                        Type 'Selected<Row>[Row[keyof Row] extends string ? keyof Row : never]' is not assignable to type '{ selected: boolean; }[(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)]'.
                          Type 'Row[keyof Row] extends string ? keyof Row : never' is not assignable to type '(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)'.
                            Type 'Row[keyof Row] extends string ? keyof Row : never' is not assignable to type 'Selected<Row>["selected"] extends string ? "selected" : never'.
                              Type 'Selected<Row>[Row[keyof Row] extends string ? keyof Row : never]' is not assignable to type 'boolean'.
                                Type 'Selected<Row>[{ [P in keyof Row]: Row[P] extends string ? P : never; }[keyof Row]]' is not assignable to type '{ selected: boolean; }[(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)]'.
                                  Type 'Row[keyof Row] extends string ? keyof Row : never' is not assignable to type '(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)'.
                                    Type 'keyof Row' is not assignable to type '(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)'.
                                      Type 'string | number | symbol' is not assignable to type '(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)'.
                                        Type 'string' is not assignable to type '(Selected<Row>[keyof Row] extends string ? keyof Row : never) & (Selected<Row>["selected"] extends string ? "selected" : never)'.
                                          Type 'string' is not assignable to type 'Selected<Row>[keyof Row] extends string ? keyof Row : never'.
                                            Type 'keyof Row' is not assignable to type 'Selected<Row>[keyof Row] extends string ? keyof Row : never'.
                                              Type 'string | number | symbol' is not assignable to type 'Selected<Row>[keyof Row] extends string ? keyof Row : never'.
                                                Type 'string' is not assignable to type 'Selected<Row>[keyof Row] extends string ? keyof Row : never'.
                                                  Type 'Row[keyof Row] extends string ? keyof Row : never' is not assignable to type 'Selected<Row>["selected"] extends string ? "selected" : never'.
                                                    Type 'Selected<Row>[{ [P in keyof Row]: Row[P] extends string ? P : never; }[keyof Row]]' is not assignable to type 'boolean'.
                                                      Type 'Selected<Row>[Row[keyof Row] extends string ? keyof Row : never]' is not assignable to type 'boolean'.
                                                        Type 'Selected<Row>[keyof Row]' is not assignable to type 'boolean'.
                                                          Type 'Row[string] | Row[number] | Row[symbol]' is not assignable to type 'boolean'.
                                                            Type 'Row[string]' is not assignable to type 'boolean'.

【问题讨论】:

    标签: typescript


    【解决方案1】:

    编译器没有你那么聪明,尤其是在推理依赖于未解析的泛型参数的条件类型时(如NonSelected&lt;Row&gt;[P] extends string ? P : never)。如果您已经检查了逻辑并确定您正在做的事情是安全的,那么明智的type assertion 是有保证的:

    const getId = <Row>(row: Selected<Row>, key: Key<Row>) =>
      row[key] as unknown as ID<Selected<Row>>;
    

    或者,你可以给编译器一些它可以推理的东西,例如由K类型的键索引的O类型的对象将产生@987654326类型的值@:

    const getId = <Row>(row: Selected<Row>, key: Key<Selected<Row>>): ID<Selected<Row>> =>
      row[key];
    

    其中任何一个都应该安抚编译器,并且您的示例继续按预期运行。

    希望对您有所帮助。祝你好运!

    【讨论】:

    • 好吧有道理。谢谢你。顺便说一句,您还回答了我最初关于如何构造Key&lt;T&gt; 类型的问题:)
    猜你喜欢
    • 2018-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多