Я думаю, что есть несколько факторов, способствующих этой ситуации.
Одна проблема заключается в том, что компилятор не выполняет сужение типа, когда проверяемый ключ свойства является переменной; см. microsoft / TypeScript # 10530 для получения дополнительной информации.
Другим фактором является то, что чтение и последующая запись свойства, тип ключа которого не известен как одиночный, не может быть безопасно выполнен. См. microsoft / TypeScript # 32693 . Компилятор не понимает, что property
- это один и тот же ключ в двух строках. Все, что он понимает, это то, что свойства, к которым осуществляется доступ, имеют один и тот же тип , который не является достаточно строгим, если типы, как известно, уже являются одноэлементными.
Наконец, компилятор просто не является отлично подходит для анализа потока управления при наличии значений, которые зависят от неопределенных параметров типа generi c, таких как T
и K
Каноническая проблема для этого - microsoft / TypeScript # 13995 . Даже если вы протестируете property
как определенный подтип keyof T
, компилятор не сузит параметр K
. В лучшем случае он сузит тип property
до некоторого пересечения K & ...
, которое может или не может быть полезным.
Одно предостережение, которое нужно здесь представить, прежде чем показывать обходные пути: это технически не безопасно. Если у вашего хоста есть свойство, тип которого известен как логический литерал false
, например:
interface Foo {
bar: false
}
declare const foo: Foo;
, тогда у вас возникнут проблемы:
setTrue(foo, "bar");
foo.bar; // false or true?
Ваша реализация setTrue()
просто проверяет, имеет ли foo.bar
тип boolean
, что и есть. Но теперь мы обманули компилятор, установив для свойства type false
значение true
. Так что foo.bar
сейчас true
во время выполнения, но false
в вашей IDE.
Я собираюсь игнорировать эту морщинку, но она все еще там, так что будьте осторожны.
Итак, обходные пути. Самым простым на данный момент является использование утверждения типа и переход к следующему:
function setTrue<T, K extends keyof T>(host: T, property: K) {
if (typeof host[property] === 'boolean')
(host as any as Record<K, boolean>)[property] = true;
}
Здесь мы утверждали, что если host[property]
имеет тип boolean
, то мы будем трактовать host
как Record<K, boolean>
: объект со значениями свойств типа boolean
в ключах типа K
.
Менее просто реорганизовать несколько небольших частей, которые компилятор может проверить сам или это можно превратить в определяемый пользователем тип защиты , чтобы провести компилятор через анализ типов так, как мы этого хотим:
function setTrueKnownBoolean<K extends PropertyKey>(host: Record<K, boolean>, property: K) {
host[property] = true;
}
function isBooleanProperty<K extends keyof T, T>(
host: T, property: K): host is T & Record<K, boolean> {
return typeof host[property] === "boolean";
}
function setTrue2<K extends keyof T, T>(host: T, property: K) {
if (isBooleanProperty(host, property)) {
setTrueKnownBoolean(host, property);
}
}
Функция setTrueKnownBoolean
только разрешено вызывать для host
объектов, которые, как известно, имеют свойства boolean
в ключе (ах) property
, поэтому реализация не требует проверки типа внутри. Функция isBooleanProperty
действует как защита типа, чтобы сузить тип host
с T
до T & Record<K, boolean>
. И теперь setTrue2()
можно реализовать, составив эти две другие функции.
Ладно, надеюсь, это поможет вам понять. Удачи!
Детская площадка ссылка на код