Я в замешательстве, когда пытаюсь понять поведение оператора &
относительно того, когда он будет добавлять/удалять свойства.
В этом примере он удаляет некоторые свойства из набора типов ключей:
[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 «расширение набора клавиш сужает объект и наоборот» Будет ли это исключением для наоборот: { A: 'a' } & { A: 'not_a' }
? Не могли бы вы также расширить объяснение наречия «потенциально»? С другой стороны, когда это возможно, я хочу знать, существует ли систематический способ вычисления результата. (На самом деле запуск машинописного компилятора не помогает, он не показывает результирующий набор пересечений.)
• «Набор ключей» для never
не определен четко, поэтому я не знаю, является ли это исключением или нет. • Хм, вы почему-то расширили сферу вопроса, спросив, как вычислить результат. Теперь вы спрашиваете две вещи. Поскольку второй «более важен», полагаю, это ваш основной вопрос? Если да, то я, вероятно, закрою это как дубликат этого. Если нет, отмените это изменение и сделайте так, чтобы задавалось только объяснение &
.
Отвечает ли это на ваш вопрос? Есть ли разница между Extract и пересечением &?
Новые примеры требуют четких объяснений, а это для нас дополнительная работа. В целом: если вы пересекаете два несовместимых типа объектов, значения этого типа не будет. Независимо от того, агрессивно ли компилятор уменьшает это значение до never
, в некотором смысле это деталь реализации. &
всегда означает пересечение, а пересечение – это всегда сужение. Пересечения ключей расширят их объекты из-за контравариантности. Пересечение объектов сузит их, возможно, до never
. Я бы написал ответ, но, думаю, существующий, вероятно, достаточно хорош.
Я имею в виду, что со временем этот вопрос становится все более актуальным, поэтому я думаю, что откажусь от него; Я предпочитаю, чтобы вопросы стабилизировались и/или сужались в ответ на комментарии, а не расширялись и менялись.
В обоих случаях оператор пересечения &
сохраняет значения, удовлетворяющие обоим операндам типа.
Что может сбить с толку, так это то, что во втором случае может быть неочевидно, что значения, удовлетворяющие либо 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?
Типы объектов контравариантны по своему набору ключей. То есть расширение набора ключей сужает объект и наоборот. Перекресток всегда сужается. Если вы используете пересечение в ключах, вы потенциально удаляете свойства, а если вы используете пересечение в самом типе объекта, вы потенциально добавляете свойства. Это полностью решает вопрос? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?