【问题标题】:Typescript Generic with implicit attribute type具有隐式属性类型的 Typescript Generic
【发布时间】:2021-05-05 01:13:43
【问题描述】:

上下文

我无法确定是否可以在类型中为泛型参数使用隐含值,这与在函数中可能使用的方式相同。

假设我有以下接口定义我的一些数据库表的结构:

interface Tables {
  'table-users': { userId: string, username: string },
  'table-posts': { postId: string, userId: string, title: string },
}

我可以编写一个过于简化的函数,允许我使用以下方法写入这些表:

const write = <T extends keyof Tables>(table: T, item: Tables[T]): boolean => {
  // ... do something
  
  return true;
}

这让我可以这样做


write('table-users', { userId: '123', username: 'John Doe' }); // ✅
write('table-posts', { postId: 'abc', userId: '123', title: 'First Post' }); // ✅

但不是

write('table-users', { postId: 'abc' }); // ❌
write('table-posts', { userId: 'abc' }); // ❌

到目前为止,一切都很好。

问题

但是,现在我希望能够构造一个类型(类似于函数),该类型需要一个需要为表名的属性,并且该类型中的另一个属性需要具有正确的项目格式。

我认为这些方面的东西应该可以工作:

type Item<T extends keyof Tables> = {
  table: T,
  item: Tables[T],
}

并自动允许:

const userItem: Item = {
  table: 'table-users',
  item: {
    userId: '1',
    username: 'John Doe',
  }
};

const postItem: Item = {
  table: 'table-posts',
  item: {
    postId: '123',
    userId: '1',
    username: 'John Doe',
  }
};

或者只是使用Item作为函数参数类型提示,但它没有,因为它总是需要显式定义表名:

const userItem: Item<'table-users'> = {
  table: 'table-users',
  item: {
    userId: '1',
    username: 'John Doe',
  }
};

const postItem: Item<'table-posts'> = {
  table: 'table-posts',
  item: {
    postId: '123',
    userId: '1',
    title: 'First Post',
  }
};

任何线索是否可以在 TypeScript 中寻找我正在寻找的东西?我认为它应该是,因为它可能是并且我可能只是在做一些愚蠢的事情,但我似乎无法修复它。

编辑

相比之下,这个有效的:

type Item = {
  [T in keyof Tables]?: {
    item: Tables[T]
  }
}

但我希望这样做,但使用 T 作为值,而不是键。

【问题讨论】:

    标签: typescript generics types


    【解决方案1】:

    经过大量的探索和测试,我想我想出了一些可能对你有帮助的东西。

    如果我对问题的理解正确,您希望您的 Item 类型将属性 table 限制为与您之前定义的 Tables 接口的键匹配的值。

    为此,您只需将您的项目类型编写如下:

    type Item = {
        table: keyof Tables,
        item: Tables[keyof Tables]
    }
    

    使用它,您的 Item 对象将其 table 属性限制为您的 Tables 接口的键。同样,您的 item 属性将被限制为您的 Tables 接口的值。

    这解决了需要通过通用&lt;&gt; 语法显式调用表名的问题。

    但是,这不会强制您的表值与项目值匹配。

    // This is allowed, but not expected as table-users doesn't allow for postId
    const postItem: Item = {
        table: 'table-users',
        item: {
            postId: '123',
            userId: '1',
            username: 'John Doe',
        }
    };
    

    如果您希望您的表值强制分配给Item.item 的允许值,您可能必须显式传递该值。

    或者,您可以更改上游逻辑,以便Item 不必引用它自己的表。这也将简化打字。

    type Item<T extends keyof Tables> = Tables[T]
    

    我认为这个问题的核心问题是Item.item 必须引用Item.table,如果不通过泛型明确提供它,就没有办法做到这一点.我们无法自行引用用户分配给 Item.table 的值,以便为与该表值对应的 Item.item 提供输入。

    【讨论】:

    • 嗨,凯文,感谢您对此进行调查。您在混合表名称和项目结构的示例中解释的问题确实是我不想要的。我也开始相信我想要的东西是不可能的。尽管它确实在函数签名中工作(如我的示例所示),但它仍然让我感到奇怪,但话又说回来,对于一个对象,您必须在创建后考虑突变。谢谢!我会让这个休息。
    【解决方案2】:

    你可以像这样使用conditional types

    type ItemGeneric<T> = T extends keyof Tables ? {
        table: T;
        item: Tables[T];
    } : never;
    
    type Item = ItemGeneric<keyof Tables>;
    

    这将根据您为table 设置的字符串值隐式设置item 的类型。 Here's a small demo 这个在行动。

    感谢nmain on github 告诉我这件事。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-01-27
      • 2020-12-06
      • 2022-01-07
      • 1970-01-01
      • 1970-01-01
      • 2016-01-25
      • 1970-01-01
      相关资源
      最近更新 更多