Тип защиты для свойства объекта generi c не работает в машинописи - PullRequest
0 голосов
/ 19 апреля 2020

Например, у меня есть очень простая функция, которая устанавливает "true" (логическое) для некоторого свойства именованного объекта:

function setTrue<T, K extends keyof T>(host: T, property: K) {
    if (typeof host[property] === 'boolean')
        host[property] = true; // <-- TS2322: Type 'true' is not assignable to type 'T[K]'.
}

Не можете понять, что не так? Похоже, что тип guard typeof host[property] === 'boolean' не работает в этом случае. Кто знает, как добиться правильной проверки типа c проверки типа в этом примере?

Единственный обходной путь, который я нашел, - это если T extends any, но в этом случае защита от типов вообще не требуется:

function setTrue<T extends any, K extends keyof T>(host: T, property: K) {
    host[property] = true; // <-- Always Ok
}

1 Ответ

0 голосов
/ 20 апреля 2020

Я думаю, что есть несколько факторов, способствующих этой ситуации.

Одна проблема заключается в том, что компилятор не выполняет сужение типа, когда проверяемый ключ свойства является переменной; см. 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() можно реализовать, составив эти две другие функции.


Ладно, надеюсь, это поможет вам понять. Удачи!

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

...