TypeScript 互斥属性的类型的定义

474 阅读1分钟

业务中常常有互斥属性的需求存在,如A,B两个属性,A与B互斥,通常我们会用可选类型去完成

type AB = {
    A?: number;
    B?: number;
}
const ab:AB = {
    A: 1,
    B: 2,
}

这么做会有个问题,那就是a和b有可能同时存在,也可能同时不存在

那换成联合类型呢

type A = {
    A: 1;
}
type B = {
    B: 1;
}

const a:A = {
    A: 1,
}

const b:B = {
    B: 1,
}

这样子确实可以完成需求了,可是要写定义两次类型不够优雅(不能偷懒),有没有什么办法可以剔除掉指定的字段再和原来的类型合并,这样子不就可以实现了吗。

首先我们要实现一个剔除指定字段的类型

 type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };

Exclude是TS的高级类型,如果 T 是 U 的子类型则返回 never 不是则返回 T

然后就可以和原来的类型合并,原来的类型需要剔除字段,可以用Omit类型完成

type XOR<T, A extends keyof T, B extends keyof T> = (Without<Omit<T, A>, Omit<T, B>> & Omit<T, B>) | (Without<Omit<T, B>, Omit<T, A>> & Omit<T, A>);
type ABC = {
    A: number,
    B: number,
    C: number,
}
//------------
//A和B互斥
const abc1: XOR<ABC, 'A', 'B'> = {
    A: 1,
    B: 2,
    C: 3,
}
//Types of property 'A' are incompatible.         Type 'number' is not assignable to type 'undefined'.
//------------

//正确写法
const abc2: XOR<ABC, 'A', 'B'> = {
    A: 1,
    C: 1,
}
const abc3: XOR<ABC, 'A', 'B'> = {
    B: 2,
    C: 1,
}

//其他属性不受影响,C必填,没有填会触发检验
const abc4: XOR<ABC, 'A', 'B'> = {
    A: 2,
}
//    Property 'C' is missing in type '{ A: number; }' but required in type 'Omit<ABC, "B">'.