Компилятор, как правило, не выполняет очень сложный анализ операций с универсальными типами (то есть типами, которые зависят от параметра неразрешенного типа, такого как T
внутри реализации func()
) ... он обычно лучше работа с конкретными типами (например, Foo
), которые более просты.
Таким образом, компилятор полностью доволен и разрешит следующую конкретную версию вашей функции:
const concreteFunc = (obj: Foo): Foo => {
for (const name in obj) {
obj[name] = 1; // okay
}
return obj; // okay
};
Поскольку неразрешенный универсальный тип еще не известен, компилятор будет менее уверен в том, что то, что вы делаете, безопасно, и может выдать предупреждение. Это предупреждение не обязательно означает, что вы определенно допустили ошибку.
Такая ситуация часто происходит внутри реализации обобщенных функций. Если вы тщательно проанализируете, что делаете, и решите, что это действительно безопасная от типа, вы можете использовать утверждения типа для удаления предупреждения.
Например, вы можете сделать это:
const func = <T extends Foo>(obj: T): T => {
for (const name in obj) {
obj[name] = 1 as T[typeof name]; // assert BUT BEWARE ☠
}
return obj;
};
Но учтите, что утверждение типа означает, что ответственность за безопасность типов перешла от компилятора к вам ... и (для ответа на ваш вопрос) это небезопасно .
И вот почему ... рассмотрим следующий код:
interface Bar extends Foo {
two: 2;
four: 4;
six: 6;
eight: 8;
}
const bar: Bar = {
two: 2,
four: 4,
six: 6,
eight: 8
};
const b = func(bar);
console.log(b.two); // 2 at compile time, but prints 1!
console.log(b.four); // 4 at compile time, but prints 1!
console.log(b.six); // 4 at compile time, but prints 1!
console.log(b.eight); // 4 at compile time, but prints 1!
Здесь мы видим интерфейс Bar
, который расширяет Foo
путем добавления известных свойств, значения которых числовые литералы , ни одно из которых не равно 1
. Когда мы вызываем func(bar)
, T
определяется как Bar
, и, следовательно, выход func(bar)
также должен быть Bar
.
И случаются плохие вещи. У нас есть объект, известные свойства которого, предположительно, являются четными числами во время компиляции, но на самом деле это число 1
во время выполнения.
Так что, вероятно, вам не следует использовать утверждения в такой функции, как func()
. Там может быть действительно безопасный способ написать func()
... например, возможно, это:
const funcSafer = <
T extends { [K in keyof T]: 1 extends T[K] ? unknown : never }
>(
obj: T
): T => {
for (const name in obj) {
obj[name] = 1 // error! still need "as T[typeof name]"
}
return obj;
};
Здесь ограничение на T
заключается в том, что 1
должен быть назначен всем его свойствам. Это имеет следующие желательные эффекты:
funcSafer(bar); // error! property "two" is incompatible
const foo: Foo = {two: 2, four: 4}; // just Foo, not Bar
funcSafer(foo); // okay
funcSafer({a: 1 as 1}); // okay
funcSafer({a: 4}); // okay, interpreted as {a: number}
funcSafer({a: 4 as 4}); // error, "a" is incompatible
Но, конечно, компилятор все еще не может сказать, что obj[name] = 1
безопасен внутри реализации. Это слишком сложно ... поэтому нам нужно утверждение.
Хорошо, надеюсь, это поможет. Удачи!
Ссылка на код