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.
ts
// instead oflet w: string | number = 5;let x: string | number = 'five';// create a type alias to reusetype StringOrNumber = string | number;let y: StringOrNumber = 5;let z: StringOrNumber = 'five';
ts
// instead oflet w: string | number = 5;let x: string | number = 'five';// create a type alias to reusetype 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.
ts
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];
ts
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
ts
/*** 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 WithPhoneNumbername: 'Malcolm',phone: 60174444444,}: {// or a WithEmailname: '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_ WithPhoneNumbername: 'Malcolm',email: 'malcolm@example.com',phone: 601744444444,};otherContactInfo.name; // NOTE: we can access anything on _either_ typeotherContactInfo.email;otherContactInfo.phone;
ts
/*** 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 WithPhoneNumbername: 'Malcolm',phone: 60174444444,}: {// or a WithEmailname: '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_ WithPhoneNumbername: 'Malcolm',email: 'malcolm@example.com',phone: 601744444444,};otherContactInfo.name; // NOTE: we can access anything on _either_ typeotherContactInfo.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:
ts
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,};
ts
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:
ts
typeAddress = {unit : string;streetOrBuilding : string;street : string;};typePersonDetails = {name : string;address :Address ;};letoldDetails :PersonDetails = {name : 'Malcolm',address : {unit : '12A',streetOrBuilding : 'Jalan Besar',street : 'Jalan',},};letnewAddress :Address = {unit : '13A',streetOrBuilding : 'Bangunan Duta',street : 'Jalan Duta',};letupdatedDetails :PersonDetails = {...oldDetails ,address :newAddress ,};
ts
typeAddress = {unit : string;streetOrBuilding : string;street : string;};typePersonDetails = {name : string;address :Address ;};letoldDetails :PersonDetails = {name : 'Malcolm',address : {unit : '12A',streetOrBuilding : 'Jalan Besar',street : 'Jalan',},};letnewAddress :Address = {unit : '13A',streetOrBuilding : 'Bangunan Duta',street : 'Jalan Duta',};letupdatedDetails :PersonDetails = {...oldDetails ,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:
ts
typePersonDetails = {name : string;address : {unit : string;streetOrBuilding : string;street : string;};};letoldDetails :PersonDetails = {name : 'Malcolm',address : {unit : '12A',streetOrBuilding : 'Jalan Besar',street : 'Jalan',},};letnewAddress :PersonDetails ['address'] = {unit : '13A',streetOrBuilding : 'Bangunan Duta',street : 'Jalan Duta',};letupdatedDetails :PersonDetails = {...oldDetails ,address :newAddress ,};
ts
typePersonDetails = {name : string;address : {unit : string;streetOrBuilding : string;street : string;};};letoldDetails :PersonDetails = {name : 'Malcolm',address : {unit : '12A',streetOrBuilding : 'Jalan Besar',street : 'Jalan',},};letnewAddress :PersonDetails ['address'] = {unit : '13A',streetOrBuilding : 'Bangunan Duta',street : 'Jalan Duta',};letupdatedDetails :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:
ts
interface Animal {name: string;greet: () => void;}type Person = {firstName: string;talk: () => void;};
ts
interface Animal {name: string;greet: () => void;}type Person = {firstName: string;talk: () => void;};
Which means you can use them as type annotation:
ts
const animal: Animal = {name: 'dog',greet: () => {console.log(`Woff! Woff!`);},};const me: Person = {firstName: 'Malcolm',talk: () => {console.log(`Hello!`);},};
ts
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:
ts
const person = Person; // Error
ts
const person = Person; // Error
Similarly, when you declare a variable, you can’t use it as type annotation:
ts
const person = {name: '',talk: () => {console.log(`Sawatika`);},};const me: person = {name: '',talk: () => {console.log(`Sawatika`);},};
ts
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:
ts
const person = {name: '',talk: () => {console.log(`Sawatika`);},};const me: typeof person = {name: '',talk: () => {console.log(`Sawatika`);},};
ts
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:
ts
constprofileImage =document .querySelector ('#profile');'profileImage' is possibly 'null'.console .log (); // Error because `src` property may not be there profileImage .src
Property 'src' does not exist on type 'Element'.18047
2339'profileImage' is possibly 'null'.
Property 'src' does not exist on type 'Element'.
ts
constprofileImage =document .querySelector ('#profile');'profileImage' is possibly 'null'.console .log (); // Error because `src` property may not be there profileImage .src
Property 'src' does not exist on type 'Element'.18047
2339'profileImage' is possibly 'null'.
Property 'src' does not exist on type 'Element'.
If we really sure that the document.querySelector
call returns an image, we can make a type assertion to tell TypeScript:
ts
constprofileImage =document .querySelector ('#profile');console .log ((profileImage asHTMLImageElement ).src );
ts
constprofileImage =document .querySelector ('#profile');console .log ((profileImage asHTMLImageElement ).src );
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.