Typescript Notes
Typescript
Typescript is a superset of Javascript, meaning that all Javascript are valid Typescript code. We can think of Typescript as giving Javascript types.
It is important to note that the code that you run at the end is actually Javascript, basically that Typescript generates Javascript that is ultimately run. So Typescript compiler is essentially a transpiler.
It is also important note that we mention that Typescript is a superset of Javascript the opposite is not true. Meaning that not all Typescript are Javascript program. So Typescript essentially can be thought of as typechecker, it helps you detect whether your Javascript code will throw an error at runtime. Note that this is not fail proof, meaning that code that actually passes the type checking of Typescript can still throw error at runtime.
Separation of Runtime
It is important to note that all type information once run through the tsc
compiler that transpiles the Typescript code to Javascript that the type information is gone. Meaning that the types in the program will not affect your generated Javascript code that is generated from the tsc
compiler and therefore you cannot use Typescript types at run time.
Structural Typing
One of the things I got confused when learning about Typescript is that it actually uses Structural Typing. Essentially this means that structure that looks the same will be compaitable as the same type.
interface Person {
first_name: string
last_name: string
}
interface PhoneContact {
first_name: string
last_name: string
contact_no: number
}
function printFullName(person: Person) {
console.log(person.first_name + " " + person.last_name)
}
You can all the function printFullName
with PhoneContact
since the required type Person
of the parameter is compaitable with the PhoneContact
as they both have a first_name
and last_name
of type primitive string. The code below is valid.
const eric: PhoneContact = {first_name: "Eric", "last_name": "Ma", "contact_no": 1234569}
printFullName(eric) // valid
Type Inference
In languages like C
and Rust
the types are static, meaning that the types do not change. This is not the correct way to think of types in Typescript, you can think of a type of a variable in Typescript as a current type at that specific point in code. You can then use control flow to refine the types this is also known as narrowing a type. Below is a basic example of you narrowing the type using a control flow if
.
type Currency = "pounds" | "dollars";
function formatCurrency(amount: number, currency: Currency) {
if (currency === "pounds") {
// inside this block, currency has type of currency: "pounds"
return `£${amount}`;
} else {
// currency is narrowed to currency: "dollars''
return `$${amount}`;
}
}
console.log(formatCurrency(100, "pounds")); // Output: £100
console.log(formatCurrency(50, "dollars")); // Output: $50
Another concrete example would be if you have a union of type string and number.
function processValue(value: string | number) {
if (typeof value === "number") {
// value: number
console.log("Number:", value.toFixed(2));
} else {
// value: string
console.log("String:", value.toLowerCase());
}
}
Although the typeof
is a runtime check the Typescript compiler understands that you have checked in the if
branch that the type is number
and in that block it narrows the type down to number
. Since our value
can only be type string | number
the else branch deduces that the type of value
in that branch can only be string