Menu

Typy przecięcia w TypeScript: łączenie typów przez &

Typ przecięcia A & B opisuje wartość, która jest jednocześnie A i B, więc ma wszystkie składowe obu. Dowiedz się, jak łączyć typy obiektowe przez &, dlaczego sprzeczne właściwości stają się never, jak przecięcia unii zostawiają tylko wspólne elementy i kiedy lepiej użyć extends.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Typ przecięcia, zapisywany jako A & B, opisuje wartość, która jest jednocześnie A i B. Dla typów obiektowych oznacza to, że wartość ma wszystkie właściwości obu. Tak łączy się istniejące typy bez ponownego wypisywania właściwości.

Drugiemu obiektowi brakuje team, więc to błąd kompilacji TS2322, a kolejna linia komunikatu mówi Property 'team' is missing ... but required in type 'Employee'. Wartość Staff można przekazać wszędzie tam, gdzie oczekiwany jest Person albo Employee.

Łączenie typów obiektowych

& działa z dowolną mieszanką aliasów typów, interfejsów i typów obiektowych zapisanych w miejscu, a także z generycznymi parametrami typu. W tym ostatnim przypadku trudno go czymś zastąpić: funkcja, która dodaje właściwości do dowolnego otrzymanego obiektu, może to dokładnie wyrazić.

Wywołujący zachowuje dokładny typ tego, co przekazał (title, words), plus dwie dodane właściwości. interface ... extends nie wyrazi „czymkolwiek jest T, plus to”, bo interfejs nie może rozszerzać parametru typu.

Sprzeczne właściwości stają się never

Gdy obie strony deklarują tę samą właściwość, jej typ jest przecięciem obu typów. Jeśli te typy nie mają żadnej wspólnej wartości, właściwość staje się never, a kompilator milczy, dopóki nie spróbujesz utworzyć wartości:

index.ts(7,22): error TS2322: Type 'string' is not assignable to type 'never'.

Błąd wskazuje na obiekt, a nie na typ, który go spowodował, więc takie bugi trudno namierzyć. Najechanie kursorem na r.id w edytorze pokazuje jego typ: never. Gdy sprzeczna właściwość jest literałowym tagiem, jak w type Shape = { kind: "circle" } & { kind: "square" }, TypeScript idzie dalej i redukuje całe przecięcie do never. Odczyt właściwości takiej wartości wyjaśnia wtedy dlaczego: Property 'kind' does not exist on type 'never'. The intersection 'Shape' was reduced to 'never' because property 'kind' has conflicting types in some constituents.

interface ... extends wyłapuje ten sam konflikt już przy deklaracji, błędem TS2430. To główna praktyczna różnica między nimi; strona o interface vs type porównuje je obok siebie.

Zgodne nakładanie się zawęża właściwość

Jeśli typy właściwości się nakładają, wynikiem jest ich część wspólna. To przydatne, a nie błąd:

Druga połowa pokazuje, co & robi z uniami: zostawia elementy wspólne dla obu stron. Myślenie o typach jak o zbiorach wartości sprawia, że jest to przewidywalne. A | B to suma obu zbiorów, A & B to ich część wspólna, a pusta część wspólna to never.

Przecięcie kontra unia

Nazwy pochodzą z teorii zbiorów i przy właściwościach obiektów wydają się odwrócone:

A | B (unia)A & B (przecięcie)
Wartość jestA albo BA i B
Zbiór dozwolonych wartościwiększymniejszy
Właściwości, których możesz użyćtylko te, które są w obuwszystkie z każdego
string z numberstring | numbernever
"a" | "b" z "b" | "c""a" | "b" | "c""b"

Przecięcie typów obiektowych ma więcej właściwości właśnie dlatego, że dopuszcza mniej wartości: tylko obiekty, które mają wszystko.

Przecięcie kontra extends

type C = A & Binterface C extends A, B
Działa zdowolnymi typami, także uniami i parametrami typutypami obiektowymi o statycznie znanych składowych
Sprzeczna właściwośćpo cichu staje się neverbłąd TS2430 albo TS2320 przy deklaracji
Wynikprzecięcie sprawdzane składnik po składnikujeden płaski nazwany typ, którego relacje są cache'owane
Duże złożeniamogą spowalniać sprawdzanie typówzalecane przez stronę Performance w wiki TypeScript

Do połączenia kilku aliasów typów obiektowych & jest idiomatyczne i w porządku. Dla typu złożonego z wielu części albo typu publicznego API extends daje wcześniejsze błędy i tańsze sprawdzanie typów.

Przecięcia z prymitywami: branding

Przecięcie prymitywu z typem obiektowym nie daje never: string & { readonly __brand: "UserId" } to string z dodatkowym znacznikiem istniejącym tylko w czasie kompilacji. Żaden prawdziwy string nie ma tej właściwości i właśnie o to chodzi: utworzyć taką wartość może tylko kod, który celowo potwierdza brand, więc zwykłego string ani OrderId nie da się już przekazać tam, gdzie oczekiwany jest UserId. Ta technika ma własną stronę, branded types.

Najczęściej zadawane pytania

Czym jest typ przecięcia w TypeScript?

To typ zapisany jako A & B, którego wartości muszą jednocześnie spełniać A i B. Dla typów obiektowych oznacza to, że wartość ma wszystkie właściwości A i wszystkie właściwości B. To typowy sposób na połączenie dwóch aliasów typów w jeden.

Czym różni się unia od przecięcia?

Unia A | B oznacza "jedno z dwóch": wartość może być A albo B, a dopóki jej nie zawęzisz, możesz używać tylko tego, co mają wspólne. Przecięcie A & B oznacza "oba naraz": wartość ma wszystko z obu. Przy typach obiektowych unia przyjmuje więcej wartości, a przecięcie ma więcej właściwości.

Dlaczego mój typ przecięcia to never?

Bo żadna wartość nie może spełnić obu stron. string & number to never, a { id: string } & { id: number } sprawia, że id jest typu string & number, więc właściwość jest never i nie da się utworzyć żadnego obiektu. Jeśli dwa typy obiektowe mają ten sam literałowy tag z różnymi wartościami (kind: "circle" i kind: "square"), całe przecięcie redukuje się do never.

Używać przecięcia czy extends?

Oba łączą typy obiektowe. interface X extends A, B zgłasza sprzeczne właściwości przy deklaracji i to je zespół TypeScript zaleca do składania dużych typów obiektowych. & działa z każdym typem, także z uniami i parametrami generycznymi, których extends nie połączy. Używaj & dla aliasów typów i generycznych funkcji pomocniczych, a extends przy budowaniu interfejsów.

Jak połączyć dwa typy obiektowe w TypeScript?

Napisz type Merged = A & B. Dla wartości w czasie działania rozwiń oba obiekty: const merged: A & B = { ...a, ...b }. Jeśli A i B mają wspólną właściwość o różnych typach, ta właściwość dostaje typ never; użyj Omit<A, keyof B> & B, gdy właściwości drugiego obiektu mają zastąpić właściwości pierwszego.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ