簡體   English   中英

在Typescript中,如何使用字符串類型聯合來約束其他類型或接口的鍵

[英]In Typescript, how to use a string type union to constraint a key from another type or interface

如果我有:

type Op = 'AND' | 'OR';

我知道我可以寫:

type Exp<T> = {
   [_ in Op]: T[];
};

這樣以下所有行均有效

let a: Exp<number> = { OR: [4] };
let b: Exp<number> = { AND: [5] };
let c: Exp<number> = { AND: [1], OR: [2, 3] };

我的問題是我可以用ab有效,而c無效的方式來編寫它嗎? 即,僅允許一個密鑰,並且該密鑰的類型為Op

即使我像這樣迭代所有的Op類型:

type Exp<T> = { AND: T[] } | { OR: T[] };

它仍然不會執行我想要的操作。 我猜這是由於聲明合並?

打字稿很新。 尋找一種優雅的方式來表達這一點。

有幾種不同的方法可以實現您想要的,哪種方法最好取決於您的實際用例,我無法從示例中得出。

這些是實現所需目標的一些方法。

方式1:傳遞密鑰。

type Exp<T, K extends Op> = Record<K, T[]>

let a: Exp<number, "OR"> = { OR: [4] }; //pass
let b: Exp<number, "AND"> = { AND: [5] }; //pass
let c: Exp<number, "And"> = { AND: [1], OR: [2, 3] }; //fail
let c: Exp<number, Op> = { AND: [1], OR: [2, 3] }; // pass

方式2:在這種情況下,使OP的每個密鑰成為XOR,您就不必再傳遞密鑰了。

export type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
export type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };

type Op = 'AND' | 'OR';

type Exp<T> = XOR<Pick<Record<Op, T[]>, "AND">, Pick<Record<Op, T[]>, "OR">>

let a: Exp<number> = { OR: [4] }; //pass
let b: Exp<number> = { AND: [5] }; //pass
let c: Exp<number> = { AND: [1], OR: [2, 3] }; //fail

方式3:請注意,這是自動生成的版本,它並不漂亮,我這樣做只是為了表明它是可能的。 無法將這個解決方案制作成無深度的無限深度,這是因為無法對Union進行迭代,但是這顯示了Depth Nine。 我之所以發布此帖子,僅是因為對此感興趣。

// add an element to the end of a tuple
type Push<L extends any[], T> =
  ((r: any, ...x: L) => void) extends ((...x: infer L2) => void) ?
  { [K in keyof L2]-?: K extends keyof L ? L[K] : T } : never

// convert a union to an intersection: X | Y | Z ==> X & Y & Z
type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

// convert a union to an overloaded function X | Y ==> ((x: X)=>void) & ((y:Y)=>void)     
type UnionToOvlds<U> = UnionToIntersection<U extends any ? (f: U) => void : never>;

// convert a union to a tuple X | Y => [X, Y]
// a union of too many elements will become an array instead
type UnionToTuple<U> = UTT0<U> extends infer T ? T extends any[] ?
  Exclude<U, T[number]> extends never ? T : U[] : never : never

// each type function below pulls the last element off the union and 
// pushes it onto the list it builds
type UTT0<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT1<Exclude<U, A>>, A> : []
type UTT1<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT2<Exclude<U, A>>, A> : []
type UTT2<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT3<Exclude<U, A>>, A> : []
type UTT3<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT4<Exclude<U, A>>, A> : []
type UTT4<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT5<Exclude<U, A>>, A> : []
type UTT5<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT6<Exclude<U, A>>, A> : []
type UTT6<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT7<Exclude<U, A>>, A> : []
type UTT7<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT8<Exclude<U, A>>, A> : []
type UTT8<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT9<Exclude<U, A>>, A> : []
type UTT9<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTTX<Exclude<U, A>>, A> : []
type UTTX<U> = []; // bail out

export type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
export type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };

type DeepXOR<Obj, T extends any[]> = {
    [K in keyof T]: T[K] extends keyof Obj
        ? XOR<Pick<Obj, T[K]>, ((..._: T) => any) extends ((_: any, ..._1: infer TAIL) => any)
            ? DeepXOR<Obj, TAIL>[0] extends never
                ? never
                : DeepXOR<Obj, TAIL>[0]
            : never
            >
        : never;
};


interface Person {
    name: string;
    age: number;
    dob: Date;
}


type PersonXOR = DeepXOR<Person, UnionToTuple<keyof Person>>[0]

const test: PersonXOR = {name: "shanon"}; // pass
const test1: PersonXOR = {age: 24}; // pass
const test2: PersonXOR = {dob: new Date()}; // pass
const test3: PersonXOR = {name: "shanon", dob: new Date()} // fail

編輯方式4:

@dragomirtitian提醒我,實際上可以通過連接每個對象/鍵對(單對)並將其與其他對的一部分相交,並將其類型值設置為Never來創建嚴格的並集。 可能是最好的最干凈的方式

type Op = 'AND' | 'OR' | "NOT";


type Exp<T> = Op extends infer D ?
    D extends any ? {
   [_ in D]: T[];
}: never : never;

type UnionKeys<T> = T extends any ? keyof T : never;
type StrictUnionHelper<T, TAll> = T extends any ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>

let a: StrictUnion<Exp<number>> = { OR: [4] }; // no error
let b: StrictUnion<Exp<number>> = { AND: [5] }; // no error
let c: StrictUnion<Exp<number>> = { AND: [1], OR: [2, 3] }; // error

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM