Typescript The Unknown And Never Types Complete Guide
Understanding the Core Concepts of TypeScript The unknown and never Types
TypeScript: The unknown
and never
Types
Understanding the unknown
Type
Definition: Introduced in TypeScript 3.0, the unknown
type represents values whose types are not known until runtime. This is a more restrictive counterpart to any
, as it enforces more type-safe operations on variables declared with unknown
.
Key Characteristics:
- Unlike variables of type
any
, those of typeunknown
cannot be assigned to variables of any other type (exceptunknown
andany
) without first performing runtime type checks or casts. - This characteristic adds layer of security against accidental type errors and helps catch many bugs during compile time rather than runtime.
Importance and Usage:
- Function Parameters: Useful when you’re writing functions that can accept any kind of input, but must validate it before processing.
- Dynamic Content Handling: Ideal for interacting with dynamic content like user-generated data, external APIs, etc.
- Refactoring: When transitioning from
any
to more specific types,unknown
serves as a safe intermediate state.
Example:
// Function that accepts any content and validates it's a string
function safelyHandleInput(input: unknown) {
if (typeof input === 'string') {
console.log(input.toUpperCase());
} else {
console.error('Invalid input type!');
}
}
safelyHandleInput("hello"); // Outputs: HELLO
safelyHandleInput(42); // Outputs: Invalid input type!
In the above example, input
has type unknown
. Before performing operations (like .toUpperCase()
), TypeScript ensures that input
has been correctly identified as a string through a conditional check.
Understanding the never
Type
Definition: The never
type signifies values that never occur. It's a subtype of every type except itself, meaning no value in TypeScript can have type never
.
Key Characteristics:
- Commonly used for function return types that always throw an exception or never properly finish.
- Also useful when narrowing down types based on conditions that exhaust all possibilities.
Importance and Usage:
- Error Handling Functions: Essential for declaring functions that intentionally throw errors and thus never return normally.
- Unreachable Code: Helps identify unreachable code segments, improving the structural integrity and readability of your code.
- Exhaustive Switch Statements: Ensures all possible cases are handled in a switch statement by forcing a compiler error if some case isn't addressed.
Example:
// Function that always throws an error
function throwError(message: string): never {
throw new Error(message);
}
// Exhaustive switch example
function determineType(value: string | number | boolean): string {
switch (typeof value) {
case 'string':
return 'String type';
case 'number':
return 'Number type';
case 'boolean':
return 'Boolean type';
default:
// Type never reached here since all possible types are already covered in cases
const _exhaustiveCheck: never = value;
return _exhaustiveCheck;
}
}
console.log(determineType("Hello")); // Outputs: String type
console.log(determineType(10)); // Outputs: Number type
In the above code, throwError
has a return type of never
, indicating it will never complete normally due to the thrown exception. In the switch statement, default
case uses never
to ensure all expected types are explicitly handled.
Summary
Both the unknown
and never
types contribute to safer and more maintainable TypeScript code:
unknown
: Makes dynamic and less controlled data interaction safer by imposing strict type guarantees.never
: Useful for handling error cases, unreachable code, and ensuring exhaustiveness of control structures like switches.
Online Code run
Step-by-Step Guide: How to Implement TypeScript The unknown and never Types
Type unknown
The unknown
type is a safe alternative to any
. It represents any value but requires you to perform some type checks before using it. This makes your code more robust and less error-prone.
Example 1: Basic Usage
Let's start with a simple example where we declare a variable of type unknown
and try to work with it:
let userInput: unknown;
userInput = 'Hello World';
userInput = 42;
userInput = true;
userInput = { key: 'value' };
In this example, userInput
can hold any type of value because it's declared as unknown
. But if you try to use the variable directly, TypeScript will throw an error unless you narrow down the type.
Error Example
// Error: Cannot assign type 'unknown' to type 'string'
let message: string = userInput;
To fix this, you need to check the type of userInput
:
Example 2: Type Guard
We can use a type guard to ensure that userInput
is a string
before assigning it to message
:
if (typeof userInput === 'string') {
let message: string = userInput; // Now TypeScript knows userInput is a string
console.log(message);
} else {
console.error('userInput is not a string');
}
Here’s what happens:
- TypeScript narrows down the type of
userInput
inside theif
block using thetypeof
operator. - Since
typeof userInput
returns'string'
, TypeScript treatsuserInput
as astring
within that block. - You can now safely assign
userInput
to astring
variable or call string-specific methods.
Example 3: Unknown vs. Any
To illustrate the difference between unknown
and any
, compare the following two examples:
Using any
let userInputAny: any;
userInputAny = 'Hello';
// No errors because TypeScript has no way to know userInputAny's type
console.log(userInputAny.toUpperCase());
Using unknown
let userInputUnknown: unknown;
userInputUnknown = 'Hello';
// Error: Property 'toUpperCase' does not exist on type 'unknown'.
console.log(userInputUnknown.toUpperCase());
if (typeof userInputUnknown === 'string') {
// Inside this block, userInputUnknown is treated as a string
console.log(userInputUnknown.toUpperCase());
} else {
console.error('userInputUnknown is not a string');
}
As you can see, unknown
forces you to add type checks before performing operations, while any
allows you to skip these checks at the cost of type safety.
Type never
The never
type represents values which are never supposed to occur. You typically use this type when a function will never return normally or when a variable will never be assigned a value.
Example 1: Functions That Never Return
A common use case for never
is functions that always throw an error or contain an infinite loop:
function throwError(errMsg: string): never {
throw new Error(errMsg);
}
function infiniteLoop(): never {
while (true) {
// do something
}
}
In both cases, TypeScript knows these functions will never return a value, hence their return type is never
.
Example 2: Type Checking
You might use never
in type guard scenarios where you have exhausted all possible cases:
type Animal = 'dog' | 'cat' | 'bird';
function getAnimalDescription(animal: Animal): string {
switch (animal) {
case 'dog':
return 'A dog is a loyal companion.';
case 'cat':
return 'Cats are independent and often mysterious.';
case 'bird':
return 'Birds can fly and sing.';
default:
// TypeScript will infer this line to be type 'never'
// because you should have covered all possible Animal types.
const invalidAnimal: never = animal;
throw new Error(`Unknown animal type: ${invalidAnimal}`);
}
}
const descriptionDog = getAnimalDescription('dog'); // Valid
const descriptionCat = getAnimalDescription('cat'); // Valid
const descriptionBird = getAnimalDescription('bird'); // Valid
// Error: Argument of type 'fish' is not assignable to parameter of type 'Animal'.
const descriptionFish = getAnimalDescription('fish'); // Invalid
In this scenario:
- The function
getAnimalDescription
takes an argument of typeAnimal
, which is a union of literal types'dog'
,'cat'
, and'bird'
. - If you try to pass a value that is not part of the
Animal
union, TypeScript will throw an error. - The
default
case includes anever
type guard that ensures you've exhaustively handled all possible values forAnimal
.
Exhaustive Type Checking
If you don't include the never
type guard:
function getAnimalDescription(animal: Animal): string {
switch (animal) {
case 'dog':
return 'A dog is a loyal companion.';
case 'cat':
return 'Cats are independent and often mysterious.';
// Missing case for 'bird'
default:
// Error: Type 'Bird' is not assignable to type 'never'.
let invalidAnimal: never = animal;
throw new Error(`Unknown animal type: ${animal}`);
}
}
This code will produce an error because the switch
statement doesn’t cover all possible Animal
values (bird
is missing).
By adding the never
type guard in the default
case, TypeScript ensures that all possible values are handled:
switch (animal) {
case 'dog':
return 'A dog is a loyal companion.';
case 'cat':
return 'Cats are independent and often mysterious.';
case 'bird':
return 'Birds can fly and sing.';
default:
let invalidAnimal: never = animal; // Ensures all cases are handled
throw new Error(`Unknown animal type: ${invalidAnimal}`);
}
With this guard, if you ever expand the Animal
type (e.g., add a new fish
literal), TypeScript will remind you to handle the new case in the switch
statement.
Example 3: Impossible State
Sometimes, a variable should never reach a certain state. For example:
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
function describeShape(shape: 'circle' | 'square'): string {
switch (shape) {
case 'circle':
return 'A circle is round.';
case 'square':
return 'A square has four equal sides.';
default:
// TypeScript will infer the 'default' case to be unreachable,
// thus, assigning the type 'never' to it.
return assertNever(shape);
}
}
In this scenario:
- The
describeShape
function takes ashape
argument that can only be'circle'
or'square'
. - If the function reaches the
default
case, it's an indication of an unexpected state—this should never happen. - The
assertNever
function is designed to throw an error, but it also has a return type ofnever
to indicate that it will never actually finish executing.
Summary
- Unknown: A type representing any value, but requires type checks before working with the value. Helps prevent type-related errors that
any
would allow. - Never: A type for values that never occur, such as functions that never return or variables that should be impossible to reach.
Login to post a comment.