Как определить функцию Typescript, которая меняет значения двух свойств объекта по имени, с проверкой совместимости типов? - PullRequest
0 голосов
/ 21 марта 2019

Я пытаюсь определить функцию, которая меняет значения двух свойств объекта по их именам, но я бы хотел, чтобы компилятор проверял совместимость типов (или, по крайней мере, проверял, чтобы оба свойства имели одинаковый тип):

function swap<T, TKey1 extends keyof T, TKey2 extends keyof T>(obj: T, key1: TKey1, key2: TKey2): void{
    let temp = obj[key1];
    obj[key1] = obj[key2]; 
    obj[key2] = temp;
}


let obj = {
    a: 1,
    b: 2,
    c: ""
}

swap(obj, "a", "b");    // good, both are numbers
swap(obj, "a", "c");    // should not compile, swapping number with string

Детская площадка TS

Я получил некоторый результат со следующим, но он требует, чтобы obj был пройден дважды.

function swap<T,
    TKey1 extends keyof T,
    TKey2 extends keyof T,
    TIn extends { [p in TKey1|TKey2]: T[TKey1] } >(_:T, obj: TIn, key1: TKey1, key2: TKey2): void{
    let temp = <any>obj[key1];
    obj[key1] = <any>obj[key2]; 
    obj[key2] = temp;
}


let obj = {
    a: 1,
    b: 2,
    c: ""
}

swap(obj, obj, "a", "b");    // good, both are numbers
swap(obj, obj, "a", "c");    // error, as expected

Детская площадка TS

В качестве альтернативы, я могу достичь желаемого результата с условными типами, если я возвращаю функцию, но второй вызов слишком легко забыть.

function swap<T,
    TKey1 extends keyof T,
    TKey2 extends keyof T>(obj: T, key1: TKey1, key2: TKey2):
                                            T[TKey1] extends T[TKey2] ? T[TKey2] extends T[TKey1] 
                                                ? () => void
                                                : never : never {

    return <any>(() => {
        let temp = <any>obj[key1];
        obj[key1] = <any>obj[key2];
        obj[key2] = temp;
    });
}


let obj = {
    a: 1,
    b: 2,
    c: ""
}

swap(obj, "a", "b")();    // good, both are numbers
swap(obj, "a", "c")();    // error, as expected

TS детская площадка

Можно ли упростить приведенные выше примеры?Могу ли я указать какой-либо тип вместо never, который будет указывать на ошибку системы типов?

PS Я знаю о [obj.a, obj.b] = [obj.b, obj.a];, но хотел бы избежать этого.

1 Ответ

0 голосов
/ 21 июля 2019

Хорошо, оказалось, что они фильтруют второй ключ с расширенными типами.

Исходный код доступен: https://github.com/IKoshelev/ts-typing-util/blob/master/src/Swap.ts

установка NPM npm i ts-typing-util

export type SwappableKeys<T, TKey1 extends keyof T> = Exclude<{
    [key in keyof T]:
    /**/ T[key] extends T[TKey1]
    /**/ ? T[TKey1] extends T[key]
    /*      */ ? key
    /*      */ : never
    /**/ : never;

}[keyof T], TKey1>;

/**
 * Swap prop values with a check that values have compatible type
 * @example
 * const t = {
 *   a: 1,
 *   b: 2,
 *   c: '',
 *   c1: '',
 *   d: { a: 5 },
 *   e: { a: 6 },
 *   f: { b: 7 },
 *   g: { a: '' }
 * }
 *
 * swap(t, 'a', 'b');
 * swap(t, 'a', 'c'); //error
 * swap(t, 'b', 'c'); //error
 * swap(t, 'a', 'a'); //error
 * swap(t, 'c', 'c1');
 * swap(t, 'd','e');
 * swap(t, 'd','f'); //error
 * swap(t, 'd','g'); //error
 **/
export function swap<T, TKey1 extends keyof T>(inst: T, key1: TKey1, key2: SwappableKeys<T, TKey1>): void {

    const buff = inst[key1] as any;
    inst[key1] = inst[key2] as any;
    inst[key2] = buff;

}

...