В определениях мне нужна особая сигнатура метода, только если универсальный тип является массивом - PullRequest
0 голосов
/ 29 декабря 2018

Вот несколько упрощенная версия определений для Knockout subscribable:

export type SubscriptionCallback<T = any, TTarget = void> = (this: TTarget, val: T) => void;

export interface Subscription {
    dispose(): void;
}

export interface SubscribableFunctions<T = any> extends Function {
    init<S extends Subscribable<any>>(instance: S): void;

    notifySubscribers(valueToWrite?: T, event?: string): void;

    // This definition is wrong
    subscribe<TTarget = void>(callback: SubscriptionCallback<Array<ArrayChange<T>>, TTarget>, callbackTarget: TTarget, event: "arrayChange"): Subscription;

    subscribe<TTarget = void>(callback: SubscriptionCallback<T, TTarget>, callbackTarget?: TTarget, event?: "change"): Subscription;
    subscribe<X = any, TTarget = void>(callback: SubscriptionCallback<X, TTarget>, callbackTarget: TTarget, event: string): Subscription;

    extend(requestedExtenders: ObservableExtenderOptions): this;
    extend<S extends Subscribable<any>>(requestedExtenders: ObservableExtenderOptions): S;

    getSubscriptionsCount(event?: string): number;
}

export interface Subscribable<T = any> extends SubscribableFunctions<T> { }

export interface ArrayChange<T = any> {
    status: "added" | "deleted" | "retained";
    value: T;
    index: number;
    moved?: number;
}

Проблема в том, что определение обратного вызова для события arrayChange неверно.Во-первых, событие arrayChange должно применяться, только если T является массивом, скажем, X[].Тогда значение, используемое интерфейсом ArrayChange, должно быть типа X, а не X[].

Как включить подпись события arrayChange, только если T является массивом ибыть в состоянии извлечь базовый тип?

1 Ответ

0 голосов
/ 29 декабря 2018

Вы можете использовать условный тип для извлечения элемента из массива.Чтобы ограничить доступность перегрузки для массивов только тем, когда тип T является массивом, мы можем добавить аннотацию типа для параметра this.Метод будет доступен только в том случае, если цель вызова соответствует ограничению, наложенному типом параметра this.

export type SubscriptionCallback<T = any, TTarget = void> = (this: TTarget, val: T) => void;

export interface Subscription {
    dispose(): void;
}

// The conditional type that extracts the array item if T is an array
type ItemIfArray<T> = T extends Array<infer U> ? U : never;

export interface SubscribableFunctions<T = any> extends Function {
    init<S extends Subscribable<any>>(instance: S): void;

    notifySubscribers(valueToWrite?: T, event?: string): void;

    // this must be something compatible with  SubscribableFunctions<any[]> which should mean a SubscribableFunctions where T is an array 
    subscribe<TTarget = void>(this: SubscribableFunctions<any[]>, callback: SubscriptionCallback<Array<ArrayChange<ItemIfArray<T>>>, TTarget>, callbackTarget: TTarget, event: "arrayChange"): Subscription;

    subscribe<TTarget = void>(callback: SubscriptionCallback<T, TTarget>, callbackTarget?: TTarget, event?: "change"): Subscription;
     // since the last overload has the event of type string it allows invocation with arrayChange so I removed it to get the error below, not 100% sure if keeping this overload is a good idea. 
    //subscribe<X = any, TTarget = void>(callback: SubscriptionCallback<X, TTarget>, callbackTarget: TTarget, event: string): Subscription;
    getSubscriptionsCount(event?: string): number;
}

export interface Subscribable<T = any> extends SubscribableFunctions<T> { }

export interface ArrayChange<T = any> {
    status: "added" | "deleted" | "retained";
    value: T;
    index: number;
    moved?: number;
}

let a: Subscribable<number>
a.subscribe(() => { }, void 0, 'arrayChange') //err overload not available for this type parameter 


let ar: Subscribable<number[]>
ar.subscribe(a => a[0].value.toExponential() , void 0, 'arrayChange') //ok
...