Удаление избыточности для типов, имеющих одинаковую форму

#typescript

#typescript

Вопрос:

Я создал надуманный пример (Typescript Playground), чтобы попытаться проиллюстрировать мою проблему. foo , bar и baz являются взаимоисключающими. Я только ищу решение, которое сохраняет XYZ в качестве типа параметр моей функции. Я уже понимаю, что этот тип X здесь подошел бы.

 type X = { foo: string; bar?: undefined; baz?: undefined }
type Y = { foo?: undefined; bar: string; baz?: undefined }
type Z = { foo?: undefined; bar?: undefined; baz: string; }
type XYZ = X | Y | Z;

function foo(xyz: XYZ): string | undefined {
    return xyz.foo;
}
  

В идеале мне нужно было бы определить только требуемые части:

 type X = { foo: string };
type Y = { bar: string };
type Z = { baz: string };
  

Но без избыточности я получаю это сообщение об ошибке:

 Property 'foo' does not exist on type 'XYZ'.
  Property 'foo' does not exist on type 'Y'.
  

Я пробовал это, но в итоге получаю типы, которые выглядят как undefined amp; string :

 type XYZ = { foo?: undefined; bar?: undefined; baz?: undefined } amp; (X | Y | Z);
  

Комментарии:

1.Если это так foo , вы говорите, что это bar должно быть undefined ? Я не думаю, что вы можете сделать это с одним типом, но я не Тициан Черникова-Драгомир, так что…

2. По крайней мере, текущий тип не позволяет создать объект, который имеет оба foo и bar . Я не уверен, что смогу ввести narrow в теле функции.

3. «Я не уверен, что смогу ввести narrow в теле функции». Должен быть какой-то способ (даже если это проверка свойств, как в моем ответе), иначе как вы пишете логику функции?

4. В этом примере похоже, что форма XYZ является { foo: string, bar: string, baz: string } , поэтому у меня просто не было безопасности типов, о которой я думал, что у меня есть.

Ответ №1:

Я думаю, вы ищете amp; (тип пересечения), а не | (тип объединения):

 type X = { foo: string };
type Y = { bar: string };
type Z = { baz: string };
type XYZ = X amp; Y amp; Z;
  

Из документации по типам пересечений:

Тип пересечения объединяет несколько типов в один. Это позволяет вам объединить существующие типы, чтобы получить единый тип, обладающий всеми необходимыми функциями. Например, Person amp; Serializable amp; Loggable это Person и Serializable и Loggable . Это означает, что объект этого типа будет содержать все элементы всех трех типов.

Работаем на игровой площадке.


Тем не менее, если вы говорите, что когда foo существует, bar и baz должно быть неопределенным, я думаю, вы бы придерживались типа пересечения, но тогда вам нужно сообщить TypeScript, что вы знаете о foo существовании, прежде чем использовать его, выполнив утверждение типа. Потому что в противном случае он не может знать, что вы имеете дело с X , а не с Y or Z . Например.:

 type X = { foo: string };
type Y = { bar: string };
type Z = { baz: string };
type XYZ = X | Y | Z;

function foo(xyz: XYZ): string | undefined {
    if ("foo" in xyz) { // Or whatever appropriate check
        return (xyz as X).foo;
    }
    return undefined;
}
  

На игровой площадке.

Я не думаю, что есть способ сделать это без утверждения типа. (Но опять же, я не Тициан Черникова-Драгомир. 🙂 )


Другой вариант — использовать перегрузки функций, но вы конкретно сказали, что хотите сохранить XYZ , и для этого все равно потребуется наличие логики для определения того, с чем вы имеете дело, с утверждениями типа.

Комментарии:

1. Я надеялся сохранить foo , bar и baz взаимоисключающие. Я только что понял, что даже в моем примере if (xyz.foo) не сужается xyz.bar до undefined .

2. @Wex — Хех, я только что понял, что это может быть то, что вы искали, и обновлял. 🙂