Чтобы вывод типов мог отличить один тип, входящий в множество union, от другого, необходимо чтобы каждый из них определял специальное поле, способное идентифицировать его уникальным образом. Данная глава расскажет, как определить подобные поля и научит как на их основе привязывать тип к лексическому окружению.
Тип Discriminated Unions (дискриминантное объединение), часто обозначаемое как Tagged Union (размеченное объединение) и так же, как и тип union (объединение), является множеством типов, перечисленных через прямую черту |. Значение, ограниченное дискриминантным объединением, может принадлежать только к одному типу из множества.
1
letv1:T1|T2|T3;
Из-за того, что все описанное ранее для типа union (глава Union, Intersection) идентично и для Tagged Union, будет более разумно не повторяться, а сделать упор на различия. Но так как полностью открыть завесу тайны Tagged Union на данный момент будет преждевременным, остается лишь описать детали, к которым рекомендуется вернуться при необходимости.
Несмотря на то, что Discriminated Union в большей степени идентичен типу Union, все же существует два отличия. Первое отличие заключается в том, что к типу Discriminated Union могут принадлежать только ссылочные типы данных. Второе отличие в том, что каждому объектному типу (также называемые варианты), составляющему Discriminated Union, указывается идентификатор варианта - дискриминант.
Помните, что вывод типов без помощи разработчика способен работать лишь с общими для всех типов признаками.
classBird{fly():void{}toString():string{return'bird';}}classFish{swim():void{}toString():string{return'fish';}}classInsect{crawl():void{}toString():string{return'insect';}}functionmove(animal:Bird|Fish|Insect):void{animal.fly();// Error -> [*]animal.swim();// Error -> [*]animal.crawl();// Error -> [*]animal.toString();// Ok -> [*]/** * [*] * * Поскольку вывод типо не может * определить, к какому конкретно * из трех типов принадлежит параметр * animal, он не позволяет обращаться к * уникальным для каждого типа членам, * коими являются методы fly, swim, crawl. * * В отличие от этих методов, метод toString * определен в каждом из возможных типов, * поэтому при его вызове ошибки не возникает. */}
Чтобы компилятор мог работать с членами, присущим конкретным типам, составляющим дискриминантное объединение, одним из способов является сужение диапазона типов при помощи дискриминанта.
Механизм, с помощью которого разработчик помогает выводу типов, называется защитники типа, и будет рассмотрен позднее в одноимённой главе (глава Защитники типа). А пока стоит сосредоточиться на самих идентификаторах вариантов.
Прежде всего стоит прояснить, что дискриминант это поле, которое обязательно должно принадлежать к литеральному типу, отличному от sunique symbol, и определенное в каждом типе, составляющем дискриминантное объединение. Кроме того, поля обязательно должны быть инициализированы при объявлении или в конструкторе. Также не будет лишним напомнить, что список литеральных типов, к которому может принадлежать дискриминант, состоит из Literal Number, Literal String, Template Literal String, Literal Boolean, Literal Enum.
1 2 3 4 5 6 7 8 91011121314151617181920212223
classBird{type:'bird'='bird';// инициализация в момент объявления поляfly():void{}}classFish{type:'fish'='fish';// инициализация в момент объявления поляswim():void{}}classInsect{type:'insect';constructor(){this.type='insect';// инициализация в конструкторе}crawl():void{}}functionmove(animal:Bird|Fish|Insect):void{}
В случае, когда типы полей являются уникальными для всего множества, они идентифицируют только свой тип.
enumAnimalTypes{Bird='bird',Fish='fish',}classBird{type:AnimalTypes.Bird=AnimalTypes.Bird;fly():void{}}classRobinextendsBird{id:0=0;}classStarlingextendsBird{id:1=1;}classFish{type:AnimalTypes.Fish=AnimalTypes.Fish;swim():void{}}classSharkextendsFish{id:0=0;}classBarracudaextendsFish{id:1=1;}declareconstanimal:Robin|Starling|Shark|Barracuda;if(animal.type===AnimalTypes.Bird){/** * В области видимости этого блока if * константа animal принадлежит к типу Bird или Starling */animal;// const animal: Robin | Starlingif(animal.id===0){/** * В области видимости этого блока if * константа animal принадлежит к типу Robin */animal;// const animal: Robin}else{/** * В области видимости этого блока else * константа animal принадлежит к типу Starling */animal;// const animal: Starling}}else{/** * В области видимости этого блока if * константа animal принадлежит к типу Shark или Barracuda */animal;// const animal: Shark | Barracudaif(animal.id===0){/** * В области видимости этого блока if * константа animal принадлежит к типу Shark */animal;// const animal: Shark}else{/** * В области видимости этого блока else * константа animal принадлежит к типу Barracuda */animal;// const animal: Barracuda}}
При необходимости декларирования поля, выступающего в роли дискриминанта, в интерфейсе ему указывается более общий совместимый тип. Для литерального строкового типа это тип string, для литерального числового это number и т. д.
1 2 3 4 5 6 7 8 91011121314151617181920212223
interfaceIT{/** * Дискриминантное поле */type:string;// это поле предполагается использовать в качестве дискриминанта поля}classAimplementsIT{type:'a'='a';// переопределение более конкретным типом}classBimplementsIT{type:'b'='b';// переопределение более конкретным типом}functionvalid(value:A|B){if(value.type==='a'){// здесь value расценивается как принадлежащее к типу A}elseif(value.type==='b'){// здесь value расценивается как принадлежащее к типу B}// здесь value расценивается как тип, обладающий общими для A и B признаками}