TypeScript for React Developer

Variables in TypeScript

Before we start our migration of React component, let's have an overview of TypeScript types and syntax.

Basic Variable and Type Declaration

ts
/**
* x is a string (inferred)
*/
// let x = "hello js";
/**
* reassignment is fine
*/
// x = "hello ts";
/**
* but it will error if we try to change type
*/
// x = 42; // Error
/**
* let's look at const. The type is literally 'hello'
*/
// const y = "hello";
/**
* This is called a 'string literal type'. y can never be reassigned since it's a const,
* so we can regard it as only ever holding a value that's literally the string 'hello world'
* and no other possible value
*/
// if (y === 'whurt') {} // this check is unnecessary
/**
* sometimes we need to declare a variable without initializing it
*/
// let z;
// z = 41;
// z = "abc"; // oh no! TypeScript didn't error
/**
* If we look at the type of z, it's `any`. This is the most flexible type
in TypeScript
*/
/**
* we could improve this situation by providing a type annotation
* when we declare our variable
*/
// let zz: number;
// zz = 41;
// zz = "abc"; // ERROR, yay!

Takeaways:

  1. TypeScript will help to infer types of a variable for you. It will try to be as strict as possible based on how JavaScript works, but not so strict that it makes you waste time on fighting the it.
  2. But TypeScript could not read your mind, so you would need to declare the type explicitly in some case.

Array and Tuple

ts
/**
* simple array types can be expressed using []
*/
let luckyNumbers: number[] = [];
luckyNumbers.push(33);
// luckyNumbers.push("abc"); // !ERROR
/* array type will be inferred as well if you provide initial value */
let luckyPersons = ['Steve', 'Bill'];
luckyPersons.push('Elon');
// luckyPersons.push(66); // ERROR
/**
* Array<> works too
*/
const frameworks: Array<string> = [];
frameworks.push('react');
// frameworks.push(true); // !ERROR
/**
* we can even define a tuple, a fixed length and specific type for each item
*/
// let address: [number, string, string, number] = [
// 123,
// "Jalan Besar",
// "Kuala Lumpur",
// 10110
// ];
// address = [1, 2, 3]; // !ERROR: Type 'number' is not assignable to type 'string'.
/**
* (Tuple values often require type annotations ( : [number, number] )
*/
// const xx = [32, 31]; // number[];
// const yy: [number, number] = [32, 31];

Takeaways:

  1. TypeScript inference will give preference to the most common usage pattern in JavaScript, e.g. prefer Array over Tuple.

Object

ts
/**
* object types can be expressed using {} and property names
*/
// let address: { houseNumber: number; streetName: string };
// address = {
// streetName: "Fake Street",
// houseNumber: 123
// };
// address = {
// houseNumber: 33
// }; // !Error: Property 'streetName' is missing
/**
* You can use the optional operator (?) to indicate that something may or may not be there
*/
// let add: { houseNumber: number; streetName?: string };
// add = {
// houseNumber: 33
// };
// Use `interface` to reuse object type
// interface Address {
// houseNumber: number;
// streetName?: string;
// }
// * and refer to it by name
// let ee: Address = { houseNumber: 33 };

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 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:

  1. Union is like OR operator. In mathematics, union is represented by two overlapping circles with both of them covered.
  2. Intersection is like AND operator. In mathematics, intersection is presented by two overlapping circles with the overlapped parts covered.

Function

ts
interface WithPhoneNumber {
name: string;
phone: number;
}
interface WithEmail {
name: string;
email: string;
}
/* function arguments and return values can have type annotations */
function sendEmail(to: WithEmail): { recipient: string; body: string } {
return {
recipient: `${to.name} <${to.email}>`, // Malcolm Kee <malcolm@example.com>
body: "You're pre-qualified for the job!",
};
}
/* the arrow-function variant */
const sendTextMessage = (
to: WithPhoneNumber
): { recipient: string; body: string } => {
return {
recipient: `${to.name} <${to.phone}>`,
body: "You're pre-qualified for the job!",
};
};
/* return types can almost always be inferred */
// function getNameParts(contact: { name: string }) {
// const parts = contact.name.split(/\s/g); // split @ whitespace
// if (parts.length < 2) {
// throw new Error(`Can't calculate name parts from name "${contact.name}"`);
// }
// return {
// first: parts[0],
// middle:
// parts.length === 2
// ? undefined
// : // everything except first and last
// parts.slice(1, parts.length - 2).join(" "),
// last: parts[parts.length - 1]
// };
// }
// * rest params work just as you'd think. Type must be array-ish
// const sum = (...vals: number[]) => vals.reduce((sum, x) => sum + x, 0);
// console.log(sum(3, 4, 6)); // 13
// * we can even provide multiple function signatures
// "overload signatures"
// function contactPeople(method: "email", ...people: WithEmail[]): void;
// function contactPeople(method: "phone", ...people: WithPhoneNumber[]): void;
// * function implementation
// function contactPeople(
// method: "email" | "phone",
// ...people: (WithEmail | WithPhoneNumber)[]
// ): void {
// if (method === "email") {
// (people as WithEmail[]).forEach(sendEmail);
// } else {
// (people as WithPhoneNumber[]).forEach(sendTextMessage);
// }
// }
// * email works
// contactPeople("email", { name: "foo", email: "" });
// * phone works
// contactPeople("phone", { name: "foo", phone: 12345678 });
// ! mixing does not work
// contactPeople("email", { name: "foo", phone: 12345678 });

any

In some case where you want to declare a variable that can be anything, make it an any.

ts
let x: any;
x = 5;
x = true;
x = {
y: true,
};

When you use any, what you tell TypeScript compiler is: "Hey this variable is so dynamic that you cannot figure out, leave me alone!". And TypeScript will let you be wild.

Try to avoid any. If your code full of any, you may just don't use TypeScript. Save yourself time to type those :any.

Comments

There is no comment on this post yet.

Issue on this page? Report here