Примитивные типы Null, Undefined, Void, Never, Unknown
Настало время рассмотреть следующую порцию типов некоторые из которых являются уникальными для TypeScript.
Важно
Прежде чем приступить к знакомству с такими типами, как Null
, Undefined
, Void
, Never
и Unknown
, стоит обговорить одну очень важную деталь. Дело в том, что все перечисленные типы можно указывать в качестве типа всем конструкциям, которые это позволяют. То есть, типом данных null
можно аннотировать даже переменную (let identifier: null
). Данная книга будет изобиловать подобными примерами, так как эта возможность облегчает демонстрацию совместимости типов. Но при этом стоит понимать, что проделывать подобное в реальном коде противопоказано.
Null (null) примитивный null тип
Примитивный тип Null
служит обозначением “ничего”.
Тип Null
указывается с помощью ключевого слова null
(не путать с единственным литеральным значением null
типа Null
, которое присваивается в качестве значения).
let identifier: null = null; // null, указанный после оператора двоеточия, это имеющийся только в TypeScript псевдоним (alias) для глобального типа Null. В, то время как null, указанный после оператора присваивания, это единственное значение типа Null.
Тип null
является подтипом только одного типа any
. Это в свою очередь означает, что значение null
может быть совместимо только с типами any
и null
, а они с ним.
let a: any = null;// Ok let b: number = null;// Error let c: string = null;// Error let d: boolean = null;// Error let e: undefined = null;// Error let f: null = null;// Ok
Тогда, когда тип данных указывается не явно, а в качестве значения используется значение null
, вывод типов определяет принадлежность к типу any
.
let identifier = null; // identifier: any
Тип null
идентичен по своей работе с одноимённым типом из JavaScript.
Undefined (undefined) примитивный неопределенный тип
Примитивный тип undefined
указывает на то, что значение не определено. Тип данных undefined
указывается с помощью ключевого слова undefined
(не путать со свойством глобального объекта undefined
, которое представляет единственное значение типа Undefined
).
let identifier: undefined = undefined; // undefined, указанный после оператора двоеточия, это имеющийся только в TypeScript псевдоним (alias) для глобального типа Undefined. В, то время как undefined, указанный после оператора присваивания, это единственное значение типа Undefined.
Во время выполнения объявленные, но не инициализированные переменные, поля и свойства класса, а также параметры имеют значение undefined
. Также значение undefined
является результатом вызова методов или функций, которые не возвращают значения.
Тип undefined
является подтипом всех типов, что делает его совместимым со всеми остальными типами.
class TypeSystem { static any: any = undefined; // Ok static number: number = undefined; // Ok static string: string = undefined; // Ok static boolean: boolean = undefined; // Ok static null: null = undefined; // Ok static undefined: undefined = undefined; // Ok }
Может возникнуть вопрос, почему тип null
, который не имеет непосредственного отношения к типу undefined
, совместим с ним? На данный момент этот вопрос так и остается неразгаданным.
В, то время как тип данных undefined
совместим со всеми типами, помимо него самого, с ним совместимы лишь null
и any
.
TypeSystem.undefined = TypeSystem.any; // Ok TypeSystem.undefined = TypeSystem.number; // Error TypeSystem.undefined = TypeSystem.string; // Error TypeSystem.undefined = TypeSystem.boolean; // Error TypeSystem.undefined = TypeSystem.null; // Ok
Тогда, когда тип данных undefined
указывается не явно, компилятор устанавливает тип any
.
let identifier = undefined; // identifier: any
При активном флаге --strictNullChecks
, тип undefined
является подтипом только одного типа any
. Поэтому его и ему в качестве значения, помимо самого себя, можно присвоить только тип any
.
class TypeSystem { static any: any = undefined; // Ok static number: number = undefined; // Error static string: string = undefined; // Error static boolean: boolean = undefined; // Error static null: null = undefined; // Error static undefined: undefined = undefined; // Ok } TypeSystem.undefined = TypeSystem.any; // Ok TypeSystem.undefined = TypeSystem.number; // Error TypeSystem.undefined = TypeSystem.string; // Error TypeSystem.undefined = TypeSystem.boolean; // Error TypeSystem.undefined = TypeSystem.null; // Error
При активном флаге --strictNullChecks
, при условии, что в качестве значения выступает значение undefined
, вывод типов определяет принадлежность к типу undefined
.
let identifier = undefined; // identifier: undefined
Тип undefined
идентичен по своей работе с одноимённым типом из JavaScript.
Void (void) отсутствие конкретного типа
Тип данных Void
можно назвать полной противоположностью типа any
, так как этот тип означает отсутствие конкретного типа. Основное предназначение типа Void
— явно указывать на то, что у функции или метода отсутствует возвращаемое значение.
Тип данных Void
указывается с помощью ключевого слова void
(не путать с одноимённым оператором из JavaScript) и, в отличие от таких типов, как null
и undefined
, не имеет никаких значений.
Тип void
является подтипом any
и супертипом для null
и undefined
.
function action(): void { } class TypeSystem { static any: any = action(); // Ok static number: number = action(); // Error static string: string = action(); // Error static boolean: boolean = action(); // Error static null: null = action(); // Error static undefined: undefined = action(); // Error static void: void = action(); // Ok } TypeSystem.void = TypeSystem.any; // Ok TypeSystem.void = TypeSystem.number; // Error TypeSystem.void = TypeSystem.string; // Error TypeSystem.void = TypeSystem.boolean; // Error TypeSystem.void = TypeSystem.null; // Ok TypeSystem.void = TypeSystem.undefined; // Ok TypeSystem.void = TypeSystem.void; // Ok
Однако с активным флагом --strictNullChecks
, тип данных void
совместим лишь с any
и undefined
.
function action(): void { } class TypeSystem { static any: any = action(); // Ok static number: number = action(); // Error static string: string = action(); // Error static boolean: boolean = action(); // Error static null: null = action(); // Error static undefined: undefined = action(); // Error static void: void = action(); // Ok } TypeSystem.void = TypeSystem.any; // Ok TypeSystem.void = TypeSystem.number; // Error TypeSystem.void = TypeSystem.string; // Error TypeSystem.void = TypeSystem.boolean; // Error TypeSystem.void = TypeSystem.null; // Error TypeSystem.void = TypeSystem.undefined; // Ok TypeSystem.void = TypeSystem.void; // Ok
Кому-то может показаться, что примеры чересчур излишни, или, что примеры, в которых результат вызова функции не имеющей возвращаемого значения присваивается полям с различными типами, не имеет никакого отношения к реальности. Да, это так. Но целью данных примеров является научить думать как компилятор TypeScript.
Когда функции в качестве возвращаемого типа указан тип void
, может показаться, что возвращая различные значения с помощью оператора return
, компилятор выбрасывает ошибки из-за понимания, что функция помечена как ничего не возвращающая. Но это не так. Ошибка возникает по причине несовместимости типов.
function a(): void { let result: number = 5; return result; // Error } function b(): void { let result: string = '5'; return result; // Error } function c(): void { let result: any = 5; return result; // Ok }
Нельзя не упомянуть, что для функций и методов, которые ничего не возвращают и у которых отсутствует аннотация типа возвращаемого значения, вывод типов определяет принадлежность к типу void
.
function action() { // function action(): void }
В отличие от null
и undefined
, тип void
не имеет ни одного значения, которое могло бы явно продемонстрировать присвоение. Однако компилятор понимает, что имеет дело с типом void
при вызове функции или метода, которые не возвращают значение. Этот становится ещё нагляднее, когда вывод типов устанавливает тип полученный при вызове функции или метода которые ничего не возвращают.
function action(): void { } let identifier = action(); // identifier: void
Тип void
является уникальным для TypeScript. В JavaScript подобного типа не существует.
Never (never) примитивный тип
Примитивный типа данных Never
служит для указания того, что какие-либо операции никогда не будут выполнены.
Never
обозначается ключевым словом never
и так же как и void
не имеет явных значений.
Тип данных never
является подтипом всех типов, что делает его совместим со всеми остальными типами.
function action(): never { throw new Error(); }; class TypeSystem { static any: any = action(); // Ok static number: number = action(); // Ok static string: string = action(); // Ok static boolean: boolean = action(); // Ok static null: null = action(); // Ok static undefined: undefined = action(); // Ok static void: void = action(); // Ok static never: never = action(); // Ok } TypeSystem.never = TypeSystem.any; // Error TypeSystem.never = TypeSystem.number; // Error TypeSystem.never = TypeSystem.string; // Error TypeSystem.never = TypeSystem.boolean; // Error TypeSystem.never = TypeSystem.null; // Error TypeSystem.never = TypeSystem.undefined; // Error TypeSystem.never = TypeSystem.void; // Error TypeSystem.never = TypeSystem.never; // Ok
Так как типу never
нельзя присвоить значение отличное от самого типа never
, единственным местом, в котором его может использовать разработчик является аннотация возвращаемого из функции или метода значения, с одной оговоркой. Тип never
можно указать только той функции, из которой программа действительно никогда не сможет выйти.
Такой сценарий может выражаться в виде функции вызов которой приведет к однозначному исключению или тело функции будет включать бесконечный цикл.
function error(message: string): never { throw new Error(message); } function loop(): never { while(true) { } }
Вывод типов определит принадлежность возвращаемого функцией значения к типу never
только если он указан в аннотации возвращаемого типа явно.
function error(message: string): never { throw new Error(message); } function action() { // function action(): never return error('All very, very bad.'); } let identifier = error(); // let identifier: never let identifier = action(); // let identifier: never
Стоит заметить, что без явного указания типа never
для декларации функции (function declaration) вывод типов определит принадлежность возвращаемого значения к типу void
.
function error(message: string) { // function error(): void throw new Error(message); } function loop() { // function loop(): void while(true) { } }
Тем не менее для функционального выражения (function expression) будет выведен тип never
.
const error = function error(message: string) { // const error: (message: string) => never throw new Error(message); } const loop = function loop() { // const loop: () => never while(true) { } }
Тип never
является уникальным для TypeScript. В JavaScript подобного типа не существует.
Unknown (unknown)
Тип Unknown
является типобезопасным аналогом типа any
и представлен в виде литерала unknown
. Все типы совместимы с типом unknown
, в, то время как сам тип unknown
совместим только с самим собой и типом any
.
class TypeSystem { static unknown: unknown; static any: any = TypeSystem.unknown; // Ok static number: number = TypeSystem.unknown; // Error static string: string = TypeSystem.unknown; // Error static boolean: boolean = TypeSystem.unknown; // Error static null: null = TypeSystem.unknown; // Error static undefined: undefined = TypeSystem.unknown; // Error static void: void = TypeSystem.unknown; // Error static never: never = TypeSystem.unknown; // Error } TypeSystem.unknown = TypeSystem.any; // Ok TypeSystem.unknown = TypeSystem.number; // Ok TypeSystem.unknown = TypeSystem.string; // Ok TypeSystem.unknown = TypeSystem.boolean; // Ok TypeSystem.unknown = TypeSystem.null; // Ok TypeSystem.unknown = TypeSystem.undefined; // Ok TypeSystem.unknown = TypeSystem.void; // Ok TypeSystem.unknown = TypeSystem.unknown; // Ok
Кроме того, над типом unknown
запрещено выполнение каких-либо операций.
let v0: any; v0.a = 5; // Ok v0.a = ''; // Ok v0(); // Ok let v1: unknown = v0; // Ok v1.a = 5; // Error v1.a = ''; // Error v1(); // Error
Если тип unknown
составляет тип пересечение (intersection
), то он будет перекрыт всеми типами.
type T0 = any & unknown; // type T0 = any type T1 = number & unknown; // type T1 = number type T2 = string & unknown; // type T2 = string type T3 = boolean & unknown; // type T3 = boolean type T4 = null & unknown; // type T4 = null type T5 = undefined & unknown; // type T5 = undefined type T6 = void & unknown; // type T6 = void type T7 = never & unknown; // type T7 = never type T8<T> = T & unknown; // type T8 = T type T9 = unknown & unknown; // type T9 = unknown
Если тип unknown
составляет тип объединение (union
), то он перекроет все типы, за исключением типа any
.
type T0 = any | unknown; // type T0 = any type T1 = number | unknown; // type T1 = unknown type T2 = string | unknown; // type T2 = unknown type T3 = boolean | unknown; // type T3 = unknown type T4 = null | unknown; // type T4 = unknown type T5 = undefined | unknown; // type T5 = unknown type T6 = void | unknown; // type T6 = unknown type T7 = never | unknown; // type T7 = unknown type T8<T> = T | unknown; // type T8 = unknown type T9 = unknown | unknown; // type T9 = unknown
Помимо этого, запрос ключей (keyof
) для типа unknown
возвращает тип never
.
type T0 = keyof number; // type T0 = "toString" | "toFixed" | "toExponential" | "toPrecision" | "valueOf" | "toLocaleString" type T1 = keyof any; // type T1 = string | number | symbol type T2 = keyof unknown; // type T2 = never
Тип unknown
позволяется использовать только в операциях равенства ===
, ==
, !==
и !=
и в операциях с логическими операторами &&
, ||
и !
.
let v0: unknown = 5; let v1 = 5 === v0; // Ok let v2 = 5 !== v0; // Ok let v3 = 5 > v0; // Error let v4 = 5 < v0; // Error let v5 = 5 >= v0; // Error let v6 = 5 <= v0; // Error let v7 = 5 - v0; // Error let v8 = 5 * v0; // Error let v9 = 5 / v0; // Error let v10 = ++v0; // Error let v11 = --v0; // Error let v12 = v0++; // Error let v13 = v0--; // Error let v14 = 5 && v0; // Ok, let v14: unknown let v15 = 5 || v0; // Ok, let v15: number let v16 = v0 || 5; // Ok, let v16: unknown let v17 = !v0; // Ok, let v17: boolean
Также стоит упомянуть, что функция у которой возвращаемый тип принадлежит к типу unknown
, может не возвращать значение явно.
function f0(): unknown { return; // Ok } function f1(): number { return; // Error } let v = f0(); // Ok, let v: unknown
При активной опции --strictPropertyInitialization
принадлежащие к типу unknown
поля не нуждаются в инициализации.
class T { f0: unknown; // Ok f1: number; // Error f2: number = 5; // Ok }
Если в определении типа данных участвует сопоставленный тип (Mapped Type
) которому в качестве аргумента типа передается тип unknown
, то такой сопоставленный тип будет выведен как объектный тип {}
. Поскольку сопоставленные типы (Mapped Types
), псевдонимы типов (types
), а также обобщения (Generics<>
) будут рассмотрены позднее, то стоит просто помнить об этом факте и повторно прочесть написанное при необходимости.
type MappedType<T> = { [K in keyof T]: T; } type T0 = MappedType<number>; // type T0 = number type T1 = MappedType<any>; // type T1 = { [x: string]: any; } type T2 = MappedType<unknown>; // type T2 = {}