在打字稿中合并类型(将键添加到现有的 keyof 类型)

Merging types in typescript (adding keys to an existing keyof type)

如果我有一个由键组成的打字稿类型:

const anObject = {value1: '1', value2: '2', value3: '3'}
type objectKeys = keyof typeof anObject

然后我希望向该类型添加键,同时保留当前键,我该怎么做?
例如,如果我想将键 'get_value1'、'get_value2'、'get_value3' 添加到类型 'objectKeys'

最后,我想要一个看起来像这样的类型:

type objectKeys = keyof anObject + 'get_value1', 'get_value2', 'get_value3'

无需手动定义前缀为 'get_' 的键,我知道我可以键入键来创建此对象 - 但这对我的用例来说不可行。我只是想向类型 'objectKeys'

添加一些可能存在或可能不存在的键

我也知道我可以创建一个泛型或任何允许任何键值的类型,但是我必须知道实际的键名。允许请求对象的任何密钥对我没有帮助,我需要现有密钥 + 我想添加的密钥。

感谢您的帮助。

为清楚起见添加:

const anObject = {val1: '1', val2: '2'}
type objectKeys = keyof typeof anObject

Object.keys(anObject).forEach(key => {
  const getAddition = `get_${key}`
  anObject[getAddition] = getAddition
})

// now I don't know whats next, how do I update objectKeys to include the 
// additions added in the forEach loop.

// What I really want is to not have to add the 'get' values to the object 
// at all, JUST to the type.  I want typechecking for the get values that 
// may or may not actually exist on the object.

希望这样更清楚。

听起来你在要求 concatenation of string literal types:也就是说,你希望能够获取字符串文字 "get_" 和另一个字符串文字 "value1",并具有TypeScript 知道,如果你连接这些类型的字符串,你会得到一个 "get_value1" 类型的字符串。不幸的是,此功能在 TypeScript 2.4 中不存在(并且可能在 2.5 或 2.6 中也不存在)。

因此无法满足您的要求并保持严格的类型安全。当然,您可以放松类型安全并允许访问任何未知密钥:

const anObject = {val1: '1', val2: '2'};
const openObject: { [k: string]: any } & typeof anObject = anObject;
// replace "any" above with whatever type the get_XXX values are

Object.keys(openObject).forEach(key => {
  const getAddition = `get_${key}`
  openObject[getAddition] = getAddition
})
openObject.val1 = 1; // error, val1 is known to be a string
openObject.get_val1 = 1; // no error, get_val1 is any
openObject.gut_val4 = 1; // no error, oops, sorry

但你说你不想那样做。


在这种情况下,我的建议是放弃向对象添加任意键,而是让 getter(或任何它们)挂在一个 get 属性,像这样:

const anObject = { val1: '1', val2: '2' }

type AnObject = typeof anObject;
type ObjectKeys = keyof AnObject;
type GetAugmentedObject = AnObject & { get: Record<ObjectKeys, any> }; 
// replace "any" above with whatever type the get.XXX values are 

const get = {} as GetAugmentedObject['get'];
Object.keys(anObject).forEach((key: ObjectKeys) => get[key] = key);
const augmentedObject: GetAugmentedObject = { ...anObject, get }

augmentedObject.val1; // ok
augmentedObject.val2; // ok 
augmentedObject.get.val1; // ok
augmentedObject.get.val2; // ok
augmentedObject.get.val3; // error, no val3
augmentedObject.git.val1; // error, no git

这对开​​发人员来说并没有太大的不同(obj.get.val1obj.get_val1),但对 TypeScript 的跟进能力有很大的不同。如果您可以控制添加键的代码,我强烈建议您做一些像这样对 TypeScript 友好的事情,因为如果没有必要,您不想花时间与 TypeScript 打交道。


否则,如果只有类型级别的字符串连接对您有用,并且您觉得您的用例足够引人注目,也许您应该去 the relevant GitHub issue 并给它一个并描述为什么它是必须的-有给你。

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

2020 更新

打字稿里有 v4.1.0

你可能需要这个 - https://github.com/microsoft/TypeScript/pull/40336

您可以使用模板文字。

这是一个示例,其中 属性 id 必须是 # + key:

interface MyInterface<K extends string> {
    something: {
        [key in K]: { id: `#${key}` }
    }
}

所以以下是正确的:

let x: MyInterface<'foo'> = {
    something: {
        foo: { id: '#foo' }
    }
}

但这是不正确的:

let y: MyInterface<'foo'> = {
    something: {
        foo: { id: 'foo' }
    }
}