英文:
Extract types from array in function argument into a generic intersection type
问题
如何确保T具有以下类型:
type CombinedFields = {
field1: string,
field2: string,
} & {
field3: string,
field4: string
}
这可以通过以下方式来实现:首先,你可以使用联合类型和泛型参数来提取数组中对象的字段类型。然后,你可以使用交叉类型将这些字段类型组合在一起。以下是示例代码:
function combineFields<T extends Extension[]>(extensions: T): T[number]['fields'] {
const combinedFields: any = {};
extensions.forEach(extension => {
for (const key in extension.fields) {
if (extension.fields.hasOwnProperty(key)) {
combinedFields[key] = extension.fields[key];
}
}
});
return combinedFields as T[number]['fields'];
}
const combinedFields = combineFields(config.extensions);
这将返回一个类型为CombinedFields
的对象,包含了所有扩展的字段。
英文:
I'm trying to extract a generic type that is a union of the types of objects present in an array passed within the argument of a function. It's easiest to illustrate this with code, so given the following:
type Extension = {
name: string,
fields: {
[key: string]: string
}
}
type Config = {
name: string,
extensions: Extension[]
}
const ext1: Extension = {
name: 'ext1',
fields: {
field1: 'test',
field2: 'test'
}
}
const ext2: Extension = {
name: 'ext1',
fields: {
field3: 'test',
field4: 'test'
}
}
const config: Config = {
name: 'Test',
extensions: [ext1, ext2]
}
// Ignore the obviously wrong typings here
function create(config: Config): T { ... }
How can I ensure that T has the following type:
type CombinedFields = {
field1: string,
field2: string,
} & {
field3: string,
field4: string
}
答案1
得分: 1
以下是翻译好的部分:
In order for this to be possible at all, you need the compiler to keep track of the particular keys and values of ext1
, ext2
, and config
. But if you annotate those variables with wide types like Extension
and Config
, you will throw away the information you need to keep track of. So you can't annotate. Instead, you can use the satisfies
operator to check that the variables are assignable to those types without widening to them:
在所有这些都成为可能之前,你需要编译器来跟踪ext1
、ext2
和config
的特定键和值。但是,如果你使用Extension
和Config
这样宽泛的类型对这些变量进行注释,那么你将丢失跟踪所需信息。所以你不能注释。相反,你可以使用satisfies运算符来检查这些变量是否可分配给这些类型而不会扩大它们:
const ext1 = {
name: 'ext1',
fields: {
field1: 'test',
field2: 'test'
}
} satisfies Extension;
/* const ext1: {
name: string;
fields: {
field1: string;
field2: string;
};
} */
const ext2 = {
name: 'ext1',
fields: {
field3: 'test',
field4: 'test'
}
} satisfies Extension;
/* const ext2: {
name: string;
fields: {
field3: string;
field4: string;
};
} */
Okay, those types know about the field names. For config
we need to go a little further; it's important for extensions
to keep track of the length and order of its elements, so it can distinguish between [ext1, ext2]
and [Math.random()<0.5 ? ext1 : ext2]
(or at least I'm assuming we need to distinguish between those). That means we should use a const
assertion on config
and allow Config
's extensions
property to be a readonly
array type because that's what const
assertions give you:
好的,这些类型了解字段名称。对于config
,我们需要再进一步;extensions
跟踪其元素的长度和顺序非常重要,这样它可以区分[ext1, ext2]
和[Math.random()<0.5 ? ext1 : ext2]
(或者至少我假设我们需要区分它们)。这意味着我们应该在config
上使用const
断言,并允许Config
的extensions
属性是一个readonly
数组类型,因为这就是const
断言提供的内容:
type Config = {
name: string,
extensions: readonly Extension[]
}
const config = {
name: 'Test',
extensions: [ext1, ext2]
} as const satisfies Config;
/* const config: {
readonly name: "Test";
readonly extensions: readonly [{
name: string;
fields: {
field1: string;
field2: string;
};
}, {
name: string;
fields: {
field3: string;
field4: string;
};
}];
} */
Okay, now that's enough information to proceed.
好的,现在有足够的信息可以继续进行。
Here's one approach:
以下是一种方法:
type ExtensionsToIntersection<T extends readonly Extension[]> =
{ [I in keyof T]: (x: T[I]["fields"]) => void }[number] extends
(x: infer I) => void ? I : never;
declare function create<T extends Config>(config: T):
ExtensionsToIntersection<T["extensions"]>;
The ExtensionsToIntersection<T>
type takes a tuple of Extension
-assignable elements and converts it to an intersection of the fields
properties of those elements. The technique is very similar to that described in the answer to https://stackoverflow.com/q/74202096/2887218. In general, the idea is to map the things we want to intersect into a contravariant type position (in this case, the parameter of a function), and then infer a single type for the union of those (inferring from the parameter position of a union of functions results in an intersection of those parameter types, as described in the release notes for conditional types).
ExtensionsToIntersection<T>
类型接受一个Extension
可分配元素的tuple,并将其转换为这些元素的fields
属性的intersection。这个技巧与https://stackoverflow.com/q/74202096/2887218的回答中描述的非常相似。总的来说,思路是将我们要交叉的内容映射到逆变类型位置(在这种情况下是函数的参数),然后推断这些联合的单一类型(从函数联合的参数位置推断会导致这些参数类型的交叉,如[条件类型的发布说明](https://www.typescriptlang.org/docs
英文:
In order for this to be possible at all, you need the compiler to keep track of the particular keys and values of ext1
, ext2
, and config
. But if you annotate those variables with wide types like Extension
and Config
, you will throw away the information you need to keep track of. So you can't annotate. Instead, you can use the satisfies
operator to check that the variables are assignable to those types without widening to them:
const ext1 = {
name: 'ext1',
fields: {
field1: 'test',
field2: 'test'
}
} satisfies Extension;
/* const ext1: {
name: string;
fields: {
field1: string;
field2: string;
};
} */
const ext2 = {
name: 'ext1',
fields: {
field3: 'test',
field4: 'test'
}
} satisfies Extension;
/* const ext2: {
name: string;
fields: {
field3: string;
field4: string;
};
} */
Okay, those types know about the field names. For config
we need to go a little further; it's important for extensions
to keep track of the length and order of its elements, so it can distinguish between [ext1, ext2]
and [Math.random()<0.5 ? ext1 : ext2]
(or at least I'm assuming we need to distinguish between those). That means we should use a const
assertion on config
and allow Config
's extensions
property to be a readonly
array type because that's what const
assertions give you:
type Config = {
name: string,
extensions: readonly Extension[]
}
const config = {
name: 'Test',
extensions: [ext1, ext2]
} as const satisfies Config;
/* const config: {
readonly name: "Test";
readonly extensions: readonly [{
name: string;
fields: {
field1: string;
field2: string;
};
}, {
name: string;
fields: {
field3: string;
field4: string;
};
}];
} */
Okay, now that's enough information to proceed.
Here's one approach:
type ExtensionsToIntersection<T extends readonly Extension[]> =
{ [I in keyof T]: (x: T[I]["fields"]) => void }[number] extends
(x: infer I) => void ? I : never;
declare function create<T extends Config>(config: T):
ExtensionsToIntersection<T["extensions"]>;
The ExtensionsToIntersection<T>
type takes a tuple of Extension
-assignable elements and converts it to an intersection of the fields
properties of those elements. The technique is very similar to that described in the answer to https://stackoverflow.com/q/74202096/2887218. In general, the idea is to map the things we want to intersect into a contravariant type position (in this case, the parameter of a function), and then infer a single type for the union of those (inferring from the parameter position of a union of functions results in an intersection of those parameter types, as described in the release notes for conditional types).
Let's test it:
const ret = create(config);
/* const ret: {
readonly field1: "test";
readonly field2: "test";
} & {
readonly field3: "test";
readonly field4: "test";
} */
Looks good!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论