Preface
In TypeScript (TS), there is a type called void
. It represents "nothing" but it's important to note that it's not the same as "nothing" in JavaScript(JS).
Typically, void
is used to declare the return type of functions. Although you can declare a variable as void
, we generally don't do this because it serves no meaningful purpose. Let's explore why in the examples below.
The void Type
Declaring a Variable as void
let name: void; // Declare a variable `name` with type `void`.
name = 'nanjiu'; // Error: Cannot assign type 'string' to type 'void'.
name = 18; // Error: Cannot assign type 'number' to type 'void'.
name = null; // Error: Cannot assign type 'null' to type 'void'.
name = undefined; // Valid
This means that when a variable has the type void
, it can only be assigned one value: undefined
. Nothing else is allowed.
Now, you can probably see why we don't typically declare variables as void
. Since its only acceptable value is undefined
, it doesn't serve any useful purpose in practical development.
Declaring Function Return Values as void
Explicit Return
When a function's return type is declared as void
, you can return undefined
inside the function:
function sayHello(): void {
console.log('hello')
return undefined
}
const str = sayHello()
console.log(str) // undefined
Besides undefined
, no other values can be returned.
Implicit Return
In JS, when you don’t explicitly return a value from a function, the implicit return value is undefined
. This makes the following code valid:
function sayHello(): void {
console.log('hello')
}
const str = sayHello()
console.log(str) // undefined
You Should Not Rely on void Values
Another key feature of void
is that callers should not rely on its return value for any operations. For example:
let name: void; // Declare a variable `name` with type `void`.
// Function with return type `void`
function sayHello(): void {
console.log('hello');
}
// Return value of function is `void`, i.e., `undefined`
const str = sayHello();
console.log(str); // undefined
// Error: Can't test the truthiness of a "void" expression.
if (str) {
console.log('str exists');
} else {
console.log('str does not exist');
}
Because void
in TS means "nothing", so you shouldn't use it for any operations.
In summary:
void
is generally used to declare the return type of functions, and it signifies "nothing. The only acceptable value for avoid
type isundefined
.- You should not rely on the return value of a
void
type for any operations.
This is quite simple and boils down to these two key points, but the following example might surprise you a bit...
type
it is a keyword in TS used to create custom types. It allows us to create aliases for any type, making type reuse and extension more convenient.
For example:
// Create a custom type that can be either a string or a number
type strOrnum = string | number;
// Declare a variable of type `strOrnum`
let str: strOrnum;
str = 'nanjiu'; // Can be assigned a string
str = 18; // Can also be assigned a number
Of course, type
has many powerful features like union types, intersection types, etc., but we won’t dive into those here. Let's look at an interesting problem:
Declaring Function Types
// Create a function type, where the parameter is a string and the return value is `void`
type say = (name: string) => void;
// Define a function with type `say`
let sayHello: say = (name: string) => {
console.log(`hello ${name}`);
};
sayHello('nanjiu');
Here, we used type
to create a function type. The function takes one parameter of type string
and returns a value of type void
.
Functions Returning Non-undefined Values
From the explanation above, we know that a function with a void
return type can only return undefined
(either explicitly or implicitly). However, in this case, you can return any value...
// Create a function type, where the parameter is a string and the return value is `void`
type say = (name: string) => void;
// Define a function with type `say`
let sayHello: say = (name: string) => {
console.log(`hello ${name}`);
return null;
};
const res = sayHello('nanjiu');
console.log(res); // null
Why does this happen? At first glance, this example seems to contradict the definition of void
. Is this a bug in TS?
Actually, it’s not. The official explanation is:
This behavior exists to ensure that the following code is valid. We know that
Array.prototype.push
returns a number, whileArray.prototype.forEach
expects its callback to have a return type ofvoid
.
const arr = [1, 2, 3, 4, 5]
const list = [0]
arr.forEach(item => list.push(item))
console.log(list)
It's effectively the same as using type
for a custom type declaration:
type callbackfn = (value: number, index: number, array: number[]) => void
This function is defined to have three parameters: the first two are of type number
, and the third is an array of numbers. The return type of the function is void
.
Since our callback function uses the arrow function shorthand, it implicitly returns list.push(item)
. The push
method, however, does have a return value.
item => list.push(item)
is equivalent to:
item => {
return list.push(item)
}
which is equivalent to:
item => {
return 2
// return 3
// return 4
// ...
}
This means the function’s return type becomes number
, which doesn't match the void
return type definition.
So, to allow this shorthand syntax, TypeScript behaves this way. When a function's return type is restricted to void
, TS won't strictly enforce that the function returns nothing.
Otherwise, we would have to write the following:
arr.forEach(item => {
list.push(item);
});
// or
arr.forEach(function(item) {
list.push(item);
});
However, even though TS doesn't strictly enforce that a void
return type must return "nothing," we still cannot rely on its return value for any operations.
// Create a function type, where the parameter is a string and the return value is `void`
type say = (name: string) => void;
// Define a function with type `say`
let sayHello: say = (name: string) => {
console.log(`hello ${name}`);
return name;
};
const res = sayHello('nanjiu');
console.log(res); // 'nanjiu'
if (res) {
console.log('res');
}
Reference: https://segmentfault.com/a/1190000045248514
Isn't this just covariance with a top type?