具有隐式属性类型的打字稿泛型

时间:2021-01-31 18:42:26

标签: typescript generics types

背景

我无法确定是否可以对类型中的泛型参数使用隐含值,就像在函数中一样。

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

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' }); // ❌

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

问题

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

我认为这些方法应该可行:

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 作为值,而不是键。

1 个答案:

答案 0 :(得分:0)

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

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

为此,您可以简单地编写您的项目类型如下:

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

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

这避免了需要通过通用 <> 语法显式调用表名的问题。

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

// 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.tablevalue 并且没有明确的方法可以做到这一点通过泛型提供它。我们没有办法自引用用户分配给 Item.table 的值,以便为与该表值对应的 Item.item 提供输入。