首页 > 解决方案 > 防止函数调用中的类型冲突

问题描述

我有很多这样的接口:

interface A {
    type: 'A';
    data: {
        a: string;
    };
}

interface B {
    type: 'B';
    data: {
        b: string;
    };
}

我有一种将它们结合在一起的类型:

type Item = A | B;

我想创建一个函数,将新项目推送到定义为const items: Item[] = [];.

到目前为止,我有这个:

const create = <I extends Item>(type: I['type'], data: I['data']) => items.push({type, data});

但是,我希望这会导致类型错误,type并且data具有冲突的类型,但它不会:

create('A', {b: '1'});

如何定义不允许create冲突type和值?data

请注意,在我的真实代码中,我的接口中的字段不仅仅是由我type的函数data生成的字段create,因此我不能简单地将 anItem作为参数。

预先感谢您的帮助!

标签: typescript

解决方案


这里的问题是在调用. 您的调用签名要求编译器推断类型和的给定值,而编译器只是没有能力做到这一点。在某一时刻,在microsoft/TypeScript#20126中进行了一些工作以启用此功能,但由于某种原因从未合并。由于没有推断,编译器放弃并扩大到它的约束,即:Icreate()II['type']I['data']IIItem

create('A', { b: '1' });
/* const create: <Item>(type: "A" | "B", data: {
    a: string;
} | {
    b: string;
}) => number */

并且没有错误,因为"A"肯定可以分配给Item["type"],并且{b: '1'}肯定可以分配给Item["data"]。这不是你想要的,所以这个调用签名对你不起作用。


编译器最擅长X从 type 的值X(而不是type 的值X['someProperty'])推断类型参数。因此,让我们这样做并从中计算您需要的类型:

const create = <K extends Item["type"]>(
  type: K, data: Extract<Item, { type: K }>['data']
) => items.push({ type, data } as Item);

在这里,我们从 推断Ktype一个 type 的值K。这被限制为Item["type"]。然后我们计算 的必要类型data,方法是获取属性为 typeExtract的联合成员,然后查看其属性。这可以按需要工作:typeKdata

create("A", { a: "1" }); // okay
create("B", { b: "1" }); // okay
create("A", { b: "1" }); // error!
// ---------> ~~~~~~
// Argument of type '{ b: string; }' is not assignable to 
// parameter of type '{ a: string; }'

这里的另一种方法是放弃泛型,只让函数接受一个休息参数,其类型是从 派生的休息元组的联合Item

type CreateArgs = Item extends infer I ? I extends Item ?
    [type: I['type'], data: I['data']] : never : never;

/* type CreateArgs = [type: "A", data: {
    a: string;
}] | [type: "B", data: {
    b: string;
}] */

在这里,我使用条件类型在联合上分配参数列表操作Item。现在create()可能看起来像这样:

const create = (...[type, data]: CreateArgs) =>
    items.push({ type, data } as Item);

它的行为与以前相似。从调用者的角度来看,它就像create()一个重载函数

create("A", { a: "1" }); // okay
create("B", { b: "1" }); // okay
create("A", { b: "1" }); // error!
// ---------> ~~~~~~
// Type '{ b: string; }' is not assignable to parameter of type '{ a: string; }'

无论哪种方式都应该适合你。


Playground 代码链接


推荐阅读