Как определить поведение оператора пересечения `&` для добавления/удаления свойств?

Я в замешательстве, когда пытаюсь понять поведение оператора & относительно того, когда он будет добавлять/удалять свойства.

В этом примере он удаляет некоторые свойства из набора типов ключей:

[K in keyof T & string]: T[K]

Ключевая часть — keyof T & string, которая удаляет из набора все нестроковые ключи.


В этом примере он добавляет/объединяет свойства из другого набора:

type Foo = { A: 'a' }
type Bar = { B: 'b' }

type FooBar = Foo & Bar // a variable of this type has both A and B.

Как видите, оператор & в обоих случаях ведет себя по-разному. Почему?

С другой стороны, компилятор TypeScript просто возвращает мне тот же тип Foo & Bar, когда я это делаю Foo & Bar, но я хотел бы знать, что получается в деталях, например. { A: 'a', B: 'b' } во втором случае.

Типы объектов контравариантны по своему набору ключей. То есть расширение набора ключей сужает объект и наоборот. Перекресток всегда сужается. Если вы используете пересечение в ключах, вы потенциально удаляете свойства, а если вы используете пересечение в самом типе объекта, вы потенциально добавляете свойства. Это полностью решает вопрос? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?

jcalz 06.04.2024 13:05

@jcalz «расширение набора клавиш сужает объект и наоборот» Будет ли это исключением для наоборот: { A: 'a' } & { A: 'not_a' }? Не могли бы вы также расширить объяснение наречия «потенциально»? С другой стороны, когда это возможно, я хочу знать, существует ли систематический способ вычисления результата. (На самом деле запуск машинописного компилятора не помогает, он не показывает результирующий набор пересечений.)

NeoZoom.lua 06.04.2024 13:45

• «Набор ключей» для never не определен четко, поэтому я не знаю, является ли это исключением или нет. • Хм, вы почему-то расширили сферу вопроса, спросив, как вычислить результат. Теперь вы спрашиваете две вещи. Поскольку второй «более важен», полагаю, это ваш основной вопрос? Если да, то я, вероятно, закрою это как дубликат этого. Если нет, отмените это изменение и сделайте так, чтобы задавалось только объяснение &.

jcalz 06.04.2024 15:02

Отвечает ли это на ваш вопрос? Есть ли разница между Extract и пересечением &?

jsejcksn 07.04.2024 03:19

Новые примеры требуют четких объяснений, а это для нас дополнительная работа. В целом: если вы пересекаете два несовместимых типа объектов, значения этого типа не будет. Независимо от того, агрессивно ли компилятор уменьшает это значение до never, в некотором смысле это деталь реализации. & всегда означает пересечение, а пересечение – это всегда сужение. Пересечения ключей расширят их объекты из-за контравариантности. Пересечение объектов сузит их, возможно, до never. Я бы написал ответ, но, думаю, существующий, вероятно, достаточно хорош.

jcalz 07.04.2024 03:45

Я имею в виду, что со временем этот вопрос становится все более актуальным, поэтому я думаю, что откажусь от него; Я предпочитаю, чтобы вопросы стабилизировались и/или сужались в ответ на комментарии, а не расширялись и менялись.

jcalz 07.04.2024 03:58
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
6
88
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В обоих случаях оператор пересечения & сохраняет значения, удовлетворяющие обоим операндам типа.

Что может сбить с толку, так это то, что во втором случае может быть неочевидно, что значения, удовлетворяющие либо Foo, либо Bar, гораздо более разнообразны, чем те, которые имеют только ключи A или B:

const fooWithAextra = {
    A: 'a',
    Aextra: 0,
} as const

const foo: Foo = fooWithAextra; // Okay, even if it contains more keys

Таким образом, набор значений, удовлетворяющих Foo, очень широк: все объекты, содержащие хотя бы ключ A (с правильным значением). Но только небольшое подмножество этих значений также удовлетворяет FooBar: объекты, которые содержат как минимум ключи A И B.

Мы могли бы сделать второй случай ближе к первому, если бы один из операндов имел несовместимый тип с другим операндом (например, keyof T содержит некоторые ключи, отличные от string):

type NotBar = { B: 'not B' }
type BarNotBar = Bar & NotBar;
//   ^? never (Bar and NotBar are incompatible)

type Foo2 = Foo | NotBar

type FooBar2 = Foo2 & Bar
//   ^? { A: 'a'; B: 'b'; } (FooBar2 does not accept any NotBar)

Таким образом, хотя это выглядит так, будто & «объединяет» свойства, на самом деле он просто сохраняет значения, которые имеют как минимум ВСЕ свойства из обоих операндов типа. Если некоторые типы несовместимы, они «удаляются» (например, ключи NotBar и не string ключи). Если все типы несовместимы, вы получите пустой набор значений, т. е. тип never (например, Bar & NotBar).

Ссылка на игровую площадку


Мне хотелось бы знать, что на самом деле представляет собой полученный набор Foo & Bar, т. е. { A: 'a', B: 'b' } во втором случае.

Вы можете использовать тип утилиты type-festSimplify, как это сделано в ссылке на игровую площадку выше:

Полезно для выравнивания вывода текста и улучшения подсказок по типу, отображаемых в редакторах. А также преобразовать интерфейс в тип, облегчающий назначаемость.

type FooBar2 = Simplify<Foo2 & Bar>
//   ^? { A: 'a'; B: 'b'; } (not just Foo2 & Bar)

Или тип Expand из связанного ответа jcalz: Как я могу увидеть полный расширенный контракт типа Typescript?

Другие вопросы по теме