因此,类型记录有一种叫做类型识别的东西:
https://www.typescriptlang.org/play#example/discriminate-types
但是,这样的事情有可能吗?
type A = {id: "a", value: boolean};
type B = {id: "b", value: string};
type AB = A | B;
const Configs = {
a: {
renderer: (data: A)=>{
// ...
}
},
b: {
renderer: (data: B)=>{
// ...
}
}
} as const;
const renderElement = (element: AB)=>{
const config = Configs[element.id];
const renderer = config.renderer;
return renderer(element);
}目前,我的最后一行与此消息失败。我或多或少知道这意味着什么,但我不知道如何解决它。
Argument of type 'AB' is not assignable to parameter of type 'never'.
The intersection 'A & B' was reduced to 'never' because property 'id' has conflicting types in some constituents.
Type 'A' is not assignable to type 'never'.(2345)更长的示例只显示了我正在工作的想法(我没有检查它是否有效,递归输入也是错误的,我还没有学习)。
type HeaderProps = { text: string, size: number, color: string};
type ImageProps = { src: string, width: number, height: string};
type GroupProps = { numberOfColumns: number, children: Item[], height: string};
type Item = {
kind: string,
data: HeaderProps | ImageProps | GroupProps
}
const itemsConfig = {
header: {
onRender: (props: HeaderProps)=>{
}
// + some other handlers...
},
image: {
onRender: (props: ImageProps)=>{
}
// + some other handlers...
},
group: {
onRender: (props: GroupProps)=>{
}
// + some other handlers...
}
} as const;
const exampleItemsPartA = [
{
kind: 'header',
data: {
text: 'Hello world',
size: 16,
}
},
{
kind: 'header',
data: {
text: 'Hello world smaller',
size: 13,
color: 'red'
}
},
{
kind: 'image',
data: {
src: 'puppy.png',
width: 100,
height: 100,
}
}
];
const exampleItems = [
...exampleItemsPartA,
{
kind: 'group',
data: exampleItemsPartA
}
]
const renderItems = (items: Item[])=>{
const result = [];
for(const item of items){
const {onRender} = itemsConfig[item.kind];
result.push(onRender(item.data))
}
return result;
}发布于 2021-12-16 03:54:03
对于原始示例和扩展示例,您都试图表示类似于“关联友联市类型”的内容,如微软/打字稿#30581中所讨论的那样。
例如,在renderElement内部,renderer值是函数的联合,而element值是函数参数的联合。但是编译器不允许您调用renderer(element),因为它没有意识到这两个联合是相互关联的。它忘记了,如果element是A类型,那么renderer将接受A类型的值。而且,由于它已经忘记了相关性,它认为renderer()的任何调用都是调用函数的联合。编译器唯一接受的参数是对联合中的每个函数类型都是安全的.一个A和一个B,或者交叉点 A & B的东西,它是never的,因为它不可能同时成为A和B
const renderElement = (element: AB) => {
const config = Configs[element.id];
const renderer = config.renderer;
/* const renderer: ((data: A) => void) | ((data: B) => void) */
return renderer(element); // error, element is not never (A & B)
}无论如何,在这两种情况下,最简单的方法就是使用类型断言告诉编译器不要担心它无法验证类型安全性:
return (renderer as (data: AB) => void)(element); // okay这里您告诉编译器,renderer实际上将接受A或B,无论调用者想要传递什么。这是一个谎言,但它是无害的,因为您知道element最终会成为renderer真正想要的类型。
直到最近,这一切才会结束。但是微软/打字稿#47109的实现是为了提供一种获得类型安全关联结合的方法。它刚刚被合并到TypeScript代码库的主要分支中,因此到目前为止,它看起来将被纳入TypeScript 4.6版本。我们可以使用夜间typescript@next构建来预览它。
下面是我们如何重写您的原始示例代码以使用修复。首先,我们编写了一个对象类型,它表示A和B的鉴别项与它们相应的data类型之间的映射:
type TypeMap = { a: boolean, b: string };然后我们可以用A、B和AB来定义TypeMap。
type AB<K extends keyof TypeMap = keyof TypeMap> =
{ [P in K]: { id: P, value: TypeMap[P] } }[K];这就是所谓的“分布式对象类型”。本质上,我们将K,一个通用类型参数受约束作为判别值,将其拆分为它的联合成员P,并在该联合上分发操作{id: P, value: TypeMap[P]}。
让我们确保这是可行的:
type A = AB<"a">; // type A = { id: "a"; value: boolean; }
type B = AB<"b"> // type B = { id: "b"; value: string; }
type ABItself = AB // { id: "a"; value: boolean; } | { id: "b"; value: string; }(请注意,当我们编写没有类型参数的AB时,它使用的是默认设置 of keyof TypeMap,也就是联合"a" | "b"。)
现在,对于configs,我们需要将其作为一个类似的映射类型,它将TypeMap转换为一个版本,其中每个属性K都有一个renderer属性,该属性是一个接受AB<K>的函数
const configs: { [K in keyof TypeMap]: { renderer: (data: AB<K>) => void } } = {
a: { renderer: (data: A) => { } },
b: { renderer: (data: B) => { } }
};这个注释是至关重要的。现在编译器可以检测到AB<K>和configs是相互关联的。如果在renderElement中使K成为泛型函数,则调用成功,因为接受AB<K>的函数将接受类型为AB<K>的值。
const renderElement = <K extends keyof TypeMap>(element: AB<K>) => {
const config = configs[element.id];
const renderer = config.renderer;
return renderer(element); // okay
}现在没有错误了。您应该能够调用renderElement,并让编译器根据传入的值推断出K:
renderElement({ id: "a", value: true });
// const renderElement: <"a">(element: { id: "a"; value: boolean; }) => void
renderElement({ id: "b", value: "okay" });
// const renderElement: <"b">(element: { id: "b"; value: string; }) => void所以这就是它的要旨。对于扩展的示例,您可以编写类型如下
type ItemMap = { header: HeaderProps, image: ImageProps, group: GroupProps };
type Item<K extends keyof ItemMap = keyof ItemMap> = { [P in K]: { kind: P, data: ItemMap[P] } }[K]
type Whatever = { a: any };
const itemsConfig: { [K in keyof ItemMap]: { onRender: (props: ItemMap[K]) => Whatever } } = {
header: { onRender: (props: HeaderProps) => { return { a: props } } },
image: { onRender: (props: ImageProps) => { return { a: props } } },
group: { onRender: (props: GroupProps) => { return { a: props } } }
};您的renderItems()函数还需要稍加重构,以便将每个item传递给一个通用回调:
const renderItems = (items: Item[]) => {
const result: Whatever[] = [];
items.forEach(<K extends keyof ItemMap>(item: Item<K>) => {
const { onRender } = itemsConfig[item.kind];
result.push(onRender(item.data))
})
return result;
}https://stackoverflow.com/questions/70369466
复制相似问题