После обновления версии машинописного текста до 3.7 я получаю ошибки типа в 2 функциях. Функции работают корректно (при добавлении @ ts-ignore все нормально).

interface A {
    x: string,
    y: number,
}

interface B {
    x: string,
    y: number,
    z: boolean,
}

function extract(input: B, keys: Array<keyof A>): Partial<A> {
    const extract: Partial<A> = {};
    keys.forEach((key: keyof A) => {
        extract[key] = input[key]; // error!
    //  ~~~~~~~~~~~~ <-- 'string | number' is not assignable to 'undefined'    
    });
    return extract;
}

function assign(target: B, source: Partial<A>): void {
    (Object.keys(source) as Array<keyof A>).forEach((key) => {
        target[key] = source[key]!; // error!
    //  ~~~~~~~~~~~ <-- 'string | number' is not assignable to type 'never'
    });
}

const test: B = { x: "x", y: 1, z: true };
console.log(extract(test, ["y"])); // -> { y: 1 }
assign(test, { x: "new" });
console.log(test); // -> { x: "new", y: 1, z: true }

Код вместе с ошибками можно найти в ts детская площадка

Есть ли способ реализовать это правильно без @ ts-ignore?

5
piotrgajow 7 Ноя 2019 в 15:51

2 ответа

Лучший ответ

Это известный критическое изменение введено в TypeScript 3.5 для предотвращения неправильной записи в индексированные типы доступа. Это имело некоторые хорошие эффекты, обнаруживая реальные ошибки, и это имело некоторые неудачные эффекты, ложно предупреждая о совершенно безопасных назначениях, как вы можете видеть.

Самый простой способ обойти это - использовать утверждение типа :

(extract as any)[key] = input[key];
(target as any)[key] = source[key];

Есть более безопасные утверждения, чем any, но их сложнее выразить.


Если вы хотите избежать утверждений типа, вам нужно использовать некоторые обходные пути. Для extract() достаточно использовать универсальную функцию обратного вызова внутри forEach() . Компилятор видит присваивание как значение и из значения идентичного универсального типа Partial<A>[K], что позволяет:

function extract(input: B, keys: Array<keyof A>): Partial<A> {
    const extract: Partial<A> = {};
    keys.forEach(<K extends keyof A>(key: K) => {
        extract[key] = input[key];
    });
    return extract;
}

Для assign() это target[key] = source[key] не будет работать даже с универсальным key типа K. Вы читаете из универсального типа NonNullable<Partial<A>[K]> и пишете в другой универсальный тип B[K]. (Я имею в виду «разные» в том смысле, что компилятор не представляет их одинаково; конечно, когда вы их оцениваете, они одного типа.) Мы можем получить идентичный тип, расширив переменную target до { {X7}} (что хорошо, потому что каждый B также является Partial<A>, если вы щуритесь и не думаете о мутациях).

Я бы сделал это так:

function assign(target: B, source: Partial<A>): void {
    const keys = Object.keys(source) as Array<keyof A>;
    const widerTarget: Partial<A> = target;
    keys.forEach(<K extends keyof A>(key: K) => {
        if (typeof source[key] !== "undefined") { // need this check
            widerTarget[key] = source[key];
        }
    });
}

Да, и я добавил, что undefined проверка, потому что assign(test, { x: "new", y: undefined }) разрешен; язык на самом деле не не различает отсутствующие из undefined.


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

Хорошо, надеюсь, что это поможет; удачи!

Ссылка на код

4
jcalz 7 Ноя 2019 в 16:23

Я нашел следующее решение с помощью вспомогательной функции:

interface A {
    x: string,
    y: number,
}

interface B {
    x: string,
    y: number,
    z: boolean,
}

// Using this method, is better accepted by the transpiler
function typedAssign<T>(source: T, target: Partial<T>, key: keyof T, force: boolean = false) {
    if (force) {
        target[key] = source[key]!;
    } else {
        target[key] = source[key];
    }
}

function extract(input: B, keys: Array<keyof A>): Partial<A> {
    const extract: Partial<Pick<B, keyof A>> = {};
    keys.forEach((key: keyof A) => {
        typedAssign(input, extract, key);
    });
    return extract;
}

function assign(target: B, source: Partial<A>): void {
    (Object.keys(source) as Array<keyof A>).forEach((key) => {
        typedAssign(source, target, key, true);
    });
}

const test: B = { x: "x", y: 1, z: true };
console.log(extract(test, ["y"])); // -> { y: 1 }
assign(test, { x: "new" });
console.log(test); // -> { x: "new", y: 1, z: true }

ТС Детская площадка: http://www.typescriptlang.org/play/?ssl = 1 & ssc = 1 & pln = 37 & pc = 53 #

0
Mor Shemesh 7 Ноя 2019 в 14:08