Type Operations
When we have a variable, we can perform operations on it. For example, we can use *
to multiply two numbers, use .split
to split a string into an array.
If we have a type, we can perform operations on it too. We’re going to discuss few of them in this lesson.
Type Alias
Type alias is the naming of type, so you can reuse it.
// instead of
let w: string | number = 5;
let x: string | number = 'five';
// create a type alias to reuse
type StringOrNumber = string | number;
let y: StringOrNumber = 5;
let z: StringOrNumber = 'five';
Type alias is not limited to primitive, you can use it to name object type as well.
type Job = {
name: string;
salary: number;
};
const a: Job = {
name: 'programmer',
salary: 0,
};
type NumberOrStringArray = Array<number | string>;
const items: NumberOrStringArray = [3, 'three', 4, 5];
Most of the thing that you can do using interface
can be done using type
, so I usually just use type
whenever possible.
Intersection & Union
/**
* Union types
* Sometimes we have a type that can be one of several things
*/
interface WithPhoneNumber {
name: string;
phone: number;
}
interface WithEmail {
name: string;
email: string;
}
let contactInfo: WithPhoneNumber | WithEmail =
Math.random() > 0.5
? {
// we can assign it to a WithPhoneNumber
name: 'Malcolm',
phone: 60174444444,
}
: {
// or a WithEmail
name: 'Malcolm',
email: 'malcolm@example.com',
};
contactInfo.name; // NOTE: we can only access the .name property (the stuff WithPhoneNumber and WithEmail have in common)
/**
* Intersection types
*/
let otherContactInfo: WithPhoneNumber & WithEmail = {
// we _must_ initialize it to a shape that's asssignable to WithEmail _and_ WithPhoneNumber
name: 'Malcolm',
email: 'malcolm@example.com',
phone: 601744444444,
};
otherContactInfo.name; // NOTE: we can access anything on _either_ type
otherContactInfo.email;
otherContactInfo.phone;
Takeaways:
- Union is like OR operator. In mathematics, union is represented by two overlapping circles with both of them covered.
- Intersection is like AND operator. In mathematics, intersection is presented by two overlapping circles with the overlapped parts covered.
Accessing Type of an Object Type Property
Sometimes you want to extract out the type of an Object Type and use it somewhere. Consider the example below:
type PersonDetails = {
name: string;
address: {
unit: string;
streetOrBuilding: string;
street: string;
};
};
let oldDetails: PersonDetails = {
name: 'Malcolm',
address: {
unit: '12A',
streetOrBuilding: 'Jalan Besar',
street: 'Jalan',
},
};
let newAddress: {
unit: string;
streetOrBuilding: string;
street: string;
} = {
unit: '13A',
streetOrBuilding: 'Bangunan Duta',
street: 'Jalan Duta',
};
let updatedDetails: PersonDetails = {
...oldDetails,
address: newAddress,
};
Note that we have duplicated the address type in two places. We can remove that duplication by creating a type for the address:
// [!code highlight:6]
type type Address = {
unit: string;
streetOrBuilding: string;
street: string;
}
Address = {
unit: string
unit: string;
streetOrBuilding: string
streetOrBuilding: string;
street: string
street: string;
};
type type PersonDetails = {
name: string;
address: Address;
}
PersonDetails = {
name: string
name: string;
address: Address
address: type Address = {
unit: string;
streetOrBuilding: string;
street: string;
}
Address; // [!code highlight]
};
let let oldDetails: PersonDetails
oldDetails: type PersonDetails = {
name: string;
address: Address;
}
PersonDetails = {
name: string
name: 'Malcolm',
address: Address
address: {
unit: string
unit: '12A',
streetOrBuilding: string
streetOrBuilding: 'Jalan Besar',
street: string
street: 'Jalan',
},
};
// [!code highlight:2]
let let newAddress: Address
newAddress: type Address = {
unit: string;
streetOrBuilding: string;
street: string;
}
Address = {
unit: string
unit: '13A',
streetOrBuilding: string
streetOrBuilding: 'Bangunan Duta',
street: string
street: 'Jalan Duta',
};
let let updatedDetails: PersonDetails
updatedDetails: type PersonDetails = {
name: string;
address: Address;
}
PersonDetails = {
...let oldDetails: PersonDetails
oldDetails,
address: Address
address: let newAddress: Address
newAddress,
};
However, sometimes this is not possible because you don’t have control over type definition (e.g. type from library) so you can’t just refactor the type definition.
The solution is to extract the type of the property like this:
type PersonDetails = {
name: string;
address: {
unit: string;
streetOrBuilding: string;
street: string;
};
};
let oldDetails: PersonDetails = {
name: 'Malcolm',
address: {
unit: '12A',
streetOrBuilding: 'Jalan Besar',
street: 'Jalan',
},
};
// [!code highlight:2]
let newAddress: PersonDetails['address'] = {
unit: '13A',
streetOrBuilding: 'Bangunan Duta',
street: 'Jalan Duta',
};
let updatedDetails: PersonDetails = {
...oldDetails,
address: newAddress,
};
Differentiating Type and Runtime: Declaration Space
One of the implicit understanding of TypeScript is that there are two types of declaration: type declaration and variable declaration.
Following are a few type declarations:
interface Animal {
name: string;
greet: () => void;
}
type Person = {
firstName: string;
talk: () => void;
};
Which means you can use them as type annotation:
const animal: Animal = {
name: 'dog',
greet: () => {
console.log(`Woff! Woff!`);
},
};
const me: Person = {
firstName: 'Malcolm',
talk: () => {
console.log(`Hello!`);
},
};
But you can’t use them as variable:
const person = Person; // Error
Similarly, when you declare a variable, you can’t use it as type annotation:
const person = {
name: '',
talk: () => {
console.log(`Sawatika`);
},
};
const me: person = {
name: '',
talk: () => {
console.log(`Sawatika`);
},
};
If what you want is to get the type of a variable and apply it to another variable, you can use the typeof
keyword:
const person = {
name: '',
talk: () => {
console.log(`Sawatika`);
},
};
const me: typeof person = {
name: '',
talk: () => {
console.log(`Sawatika`);
},
};
Escape Hatch: Type Assertion
Sometimes, some values are really dynamic that there are no ways for TypeScript to do type-checking for you.
Consider the following example:
const const profileImage: Element | null
profileImage = var document: Document
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)document.ParentNode.querySelector<Element>(selectors: string): Element | null (+4 overloads)
Returns the first element that is a descendant of node that matches selectors.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/querySelector)querySelector('#profile');
var console: Console
The `console` module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
* A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream.
* A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and
[`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module.
_**Warning**_: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for
more information.
Example using the global `console`:
```js
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
```
Example using the `Console` class:
```js
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)
Prints to `stdout` with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html)
(the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)).
```js
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
```
See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.log(profileImage.src); // Error because `src` property may not be there
If we really sure that the document.querySelector
call returns an image, we can make a type assertion to tell TypeScript:
const const profileImage: Element | null
profileImage = var document: Document
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)document.ParentNode.querySelector<Element>(selectors: string): Element | null (+4 overloads)
Returns the first element that is a descendant of node that matches selectors.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/querySelector)querySelector('#profile');
var console: Console
The `console` module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
* A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream.
* A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and
[`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module.
_**Warning**_: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for
more information.
Example using the global `console`:
```js
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
```
Example using the `Console` class:
```js
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)
Prints to `stdout` with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html)
(the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)).
```js
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
```
See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.log((const profileImage: Element | null
profileImage as HTMLImageElement).HTMLImageElement.src: string
The address or URL of the a media resource that is to be considered.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/src)src); // [!code highlight]
Note that type assertion does nothing in runtime; if the document.querySelector
calls above returns a div
instead of a img
, the call could cause error.