Я использую функцию, которая возвращает предикат типа, чтобы сообщить TypeScript, что источник определенно имеет или не имеет свойства media
. Если это SourceWithoutMedia
, его следует обрабатывать одним способом, а если это SourceWithMedia
, его следует обрабатывать другим способом.
Проблема в том, что TypeScript делает из случая, когда предикат типа возвращает false.
Я бы ожидал, что TypeScript сделает вывод, что если isSourceWithoutMedia(source)
возвращает false, то source
является SourceWithMedia
.
На самом деле он подразумевает, что если isSourceWithoutMedia(source)
возвращает false, то тип source
— never
.
Чего мне здесь не хватает, и есть ли способ заставить TypeScript сделать вывод, что если isSourceWithoutMedia(source)
возвращает false, то source
является SourceWithMedia
?
interface SourceWithMedia {
id: string
someOtherProperty: string
media?: string
}
interface SourceWithoutMedia {
id: string
}
const isSourceWithoutMedia = (source: SourceWithMedia | SourceWithoutMedia): source is SourceWithoutMedia => {
return Object.keys(source).length === 1
}
function doSomethingWithMedia (source: SourceWithMedia | SourceWithoutMedia) {
if (isSourceWithoutMedia(source)) return source
if (source.media) return source.media // Error: Property 'media' does not exist on type 'never'.
}
TypeScript не понимает взаимосвязь между первым условным блоком и вторым.
Решением этой проблемы было бы явное приведение источника к SourceWithMedia
.
function doSomethingWithMedia (source: SourceWithMedia | SourceWithoutMedia) {
if (isSourceWithoutMedia(source)) return source
const sourceWithMedia = source as SourceWithMedia
if (sourceWithMedia.media) return sourceWithMedia.media;
}
... если isSourceWithoutMedia(source) возвращает false, то source является ИсточникСМедиа?
Нет.
Проблема в том, что SourceWithMedia
расширяет SourceWithoutMedia
i. е. SourceWithMedia
— это подтип SourceWithoutMedia
.
const withMedia: SourceWithMedia = {id: "", someOtherProperty: "", media: ""}
const withoutMedia = withMedia; // valid
При попытке сузить source
до SourceWithoutMedia
с использованием этого предиката на самом деле ничего не происходит. source
по-прежнему будет иметь тип объединения, даже если вы используете что-то вроде !("media" in source)
в своем предикате или сначала проверяете отсутствие отсутствующего параметра media
, например:
function doSomethingWithMedia(source: SourceWithMedia | SourceWithoutMedia) {
if (isSourceWithoutMedia(source)) {
source;
//^? (parameter) source: SourceWithMedia | SourceWithoutMedia
}
}
Теперь, если мы последуем за отрицательной ветвью утверждения if
, у нас не останется членов профсоюза, которых можно было бы сузить. TypeScript представляет собой пустые объединения с типом никогда.
С другой стороны, если вы инвертируете логику предикатов и проверите, присутствует ли свойство media
явно, вы сможете сузить source
до SourceWithMedia
. Таким образом, TypeScript действительно может различать эти два типа и сообщать, что мы имеем дело со значением типа SourceWithMedia
, потому что SourceWithoutMedia
нельзя присвоить SourceWithMedia
.
TypeScript, возможно, и не идеален, но это определенно не языковая проблема.