簡體   English   中英

使用泛型和keyof時推斷回調參數的屬性類型

[英]Deduce property type of callback argument when using generics and keyof

我正在努力編寫一個代碼來推斷if范圍內的args.value類型:

class Foo {
    public id: number;
    public name: string;
    public birth: Date;
}

interface ISetEventArgs<T> {
    field: keyof T;
    value: T[keyof T];
}

function bind<T>(obj: T, event: "set", handler: (args: ISetEventArgs<T>) => void): void {
    // Void
}

let f: Foo = new Foo();

bind<Foo>(f, "set", (args: IArgs<Foo>): void => {
    if (args.field === "id") {
        let id: number = args.value; // Error: Type 'string | number | Date' is not assignable to type 'number'.
    }
    else if (args.field === "name") {
        // ...
    }
    else if (args.field === "birth") {
        // ...
    }
});

我試圖通過寫這樣的東西來解決這種情況,但感覺不對:

function getValue<T, K extends keyof T>(value: T[keyof T], key: K): T[K] {
    return value;
}

// Usage:
if (args.field === "id") {
    let id: number = getValue<Foo, "id">(args.value, args.field); // Correct type.
    // Can also be used as: getValue<Foo, "id">(args.value, "id");
}

有任何想法嗎? 即使解決方案需要使用輔助函數,我真的希望能夠以更干凈的方式使用它,例如(如果可能) getValue<Foo, "id">(args.value)getValue(args.value, args.field)

我認為沒有輔助函數就無法完成 - 打字稿類型推斷沒有考慮到fieldvalue類型是相互依賴的。

所以你必須使用所謂的用戶自定義類型保護函數來顯式表達類型關系:

class Foo {
    public id: number;
    public name: string;
    public birth: Date;
}

interface ISetEventArgs<T> {
    field: keyof T;
    value: T[keyof T];
}

function bind<T>(obj: T, event: "set", handler: (args: ISetEventArgs<T>) => void): void {
    // Void
}

let f: Foo = new Foo();

// type guard
function argsForField<T, F extends keyof T>(args: ISetEventArgs<T>, field: F):
         args is { field: F; value: T[F]} {
    return args.field === field;
}

bind<Foo>(f, "set", (args: ISetEventArgs<Foo>): void => {
    if (argsForField(args, "id")) {
        let id: number = args.value; //no error
    }
    else if (argsForField(args, "name")) {
        let name: string = args.value
    }
    else if (argsForField(args, "birth")) {
        let birth: Date = args.value;
    }
});

這個問題一整天都在困擾着我,所以我玩弄了它,雖然我沒有解決方案(可悲的是),但我確實發現了一些有趣的行為,這些行為可能對您有幫助,也可能沒有幫助,具體取決於您的確切用途案件。

TL;DR:你可以在 Foo 的特定情況下得到你想要的東西,但不是一般的。 這似乎是打字稿部分的限制。

首先,讓我們將ISetEventArgs的字段和值ISetEventArgs在一起:

interface ISetEventArgs<T, K extends keyof T> {
    field: K;
    value: T[K];
}

現在,問題是類型:

ISetEventArgs<Foo, keyof Foo>

決定:

ISetEventArgs<Foo, "id"|"name|"birth">

但我們希望它是:

ISetEventArgs<Foo, "id"> | ISetEventArgs<Foo, "name"> | ISetEventArgs<Foo, "birth">

因為在第二種情況下,我們可以利用 typescript 的可區分聯合功能。 在我看來,這些在語義上是相同的,但 typescript 只會縮小第二種情況。 所以我們需要做一些惡作劇來讓它變成那種形式。

所以,如果我們定義一個類型:

type FooArgs = {[K in keyof Foo]: ISetEventArgs<Foo, K>}[keyof Foo]

這解決了我們想要的......但遺憾的是,如果我們嘗試擴展這種模式,使其適用於任何類型:

type GenericArgs<T> = {[K in keyof T]: ISetEventArgs<T, K>}[keyof T];
type GenricFooArgs = GenericArgs<Foo>;

突然GenericFooArgs解析為上面的第一種類型,而不是第二種?! 我不知道為什么手動聲明FooArgs與使用GenericArgs<Foo>

因此,如果您使用FooArgs代替ISetEventArgs<T> ,那么您將在實現處理程序時獲得所需內容。 但是......你已經失去了bind的通用能力,所以它可能根本不是一個值得的交易。

暫無
暫無

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

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