# TypeScript
###### tags: `typescript`
## What is TypeScript
TypeScript is an **syntactic superset** of JavaScript.
The Goal is add **Types** into JavaScript.
- Compiles to readable JS
- Three parts: Language, Language Server and Compiler
- Kind of like a fancy linter
## Why need Types
```javascript=
function add(a, b) {
return a + b;
}
```
```javascript=
function add(a, b, c = 0) {
return a + b + c;
}
```
### Types make the author's intent more clear
```typescript=
function add(a: number, b: number): number {
return a + b;
}
```
### Move some kinds of errors from runtime to compile time.
- Values that are null or undefined
- Incomplete refactoring
- Breakage around internal code contracts (e.g. an argument becomes required)
### Great code authoring experience
## TSC compiler
```json=
{
"devDependencies": {
"typescript": "^4.3.2"
},
"scripts": {
"dev": "tsc --watch --preserveWatchOutput"
}
}
```
```json=5
{
"compilerOptions": {
"outDir": "dist",
"target": "ES3"
},
"include": ["src"]
}
```
```json=
{
"compilerOptions": {
"outDir": "dist",
"target": "ES2015"
},
"include": ["src"]
}
```
```json=
{
"compilerOptions": {
"outDir": "dist",
"target": "ES2017"
},
"include": ["src"]
}
```
Legacy Browser support pipeline: Typescript ES2017 => Babal
`.d.ts` known as a declaration file.
```typescript=
/**
* Add three numbers
* @param a first number
* @param b second
*/
export declare function addNumbers(a: number, b: number): Promise<number>;
```
A good way to think of TS files:
- `.ts` files contain both type information and code
- `.js` files contain only code
- `.d.ts` files contain only type information
## Variables and Values
```typescript=
let age = 6;
age = "not a number";
```
TypeScript is able to infer that `age` is a number,
based on the value as we are declaring.
In TypeScript, variables are "born" with their types.
```typescript=
const age = 6;
```
TS is able to make a more specific:
- `const` variable declarations cannot be reassigned
- the initial value assigned to `age` is a number, which is an immutable value.
### Literal Types
The type `6` is called a **literal type**.
### Implicit `any` and type annotations
```typescript=
const RANDOM_WAIT_TIME = Math.round(Math.random() * 500) + 500;
let startTime = new Date();
let endTime;
setTimeout(() => {
endTime = 0;
endTime = new Date();
}, RANDOM_WAIT_TIME);
```
`endTime` is "born" without a type, so it ends up being an implicit `any`.
If we wanted more safety here, we could be add a **type annotation**:
```typescript=
const RANDOM_WAIT_TIME = Math.round(Math.random() * 500) + 500;
let startTime = new Date();
let endTime: Date;
setTimeout(() => {
// endTime = 0;
endTime = new Date();
}, RANDOM_WAIT_TIME);
```
## Function arguments and return values
```typescript=
function add(a: number, b: number): number {}
```
This is great way for code authors to state their intentions up-front.
**explicit return type**
**TypeScript is not a replacement for unit test**
## Objects
Object types are defined by:
- The `names` of the properties that are (or may be) present.
- The `types` of those properties
```typescript=
let car = {
make: "Toyota",
model: "Corolla",
year: 2002
}
```
```typescript=
{
make: string,
model: string,
year: number
}
```
### Optional Properties
```typescript=
{
make: string,
model: string,
year: number,
chargeVoltage?: number
}
```
```typescript=
function printCar(car: {
make: string,
model: string,
year: number,
chargeVoltage?: number
}) {
let str = `${car.make} ${car.model} ${car.year}`;
if (typeof car.chargeVoltage !== 'undefined') {
str += `// ${car.chargeVoltage}`;
}
console.log(str);
}
```
**Compare with this**
```typescript=
{
make: string,
model: string,
year: number,
chargeVoltage: number | undefined
}
```
## Excess property checking
```typescript=
function printCar(car: {
make: string,
model: string,
year: number,
chargeVoltage?: number
}) {
}
printCar({
make: "Tesla",
model: "Model 3",
year: 2020,
chargeVoltage: 220,
color: "RED" // <==== Extra Property
})
```
**Compare with this**
```typescript=
function printCar(car: {
make: string,
model: string,
year: number,
chargeVoltage?: number
}) {
}
const myCar = {
make: "Tesla",
model: "Model 3",
year: 2020,
chargeVoltage: 220,
color: "RED" // <==== Extra Property
};
printCar(myCar);
```
### Index signatures
```typescript=
{
[k: string]: {
country: string,
area: string,
number: string
}
}
```
### Array
```typescript=
const fileExtensions = ["js", "ts"]; // => string[]
```
### Tuples
```typescript=
let myCar = [2002, "Toyota", "Corolla"]; // => (string | number)[]
const [year, make, model] = myCar;
```
**Compare to**
```typescript=
let myCar: [number, string, string] = [2002, "Toyota", "Corolla"];
const [year, make, model] = myCar;
```
**Consider**
```typescript=
const numPair: [number, number] = [4, 5, 6]; // Error
```
```typescript=
const numPair: [number, number] = [4, 5];
numPair.push(6);
numPair.pop();
numPair.pop();
numPair.pop();
```
## Structural vs Nominal Types
### What is type checking?
attempts to asked the question of compatibility or type equivalence
```typescript=
function foo(x) {
}
// TYPE CHECKING
// Is `myValue` type-equivalent to what `foo` want to receive?
foo(myValue)
```
### Static vs Dynamic
type-checking is performed at **compile time** or **runtime**
**TypeScript's type system is static**
### Nominal vs structural
Nominal type systems are care about NAMES. Such as `JAVA`
Structural type systems are care about SHAPES. Such as `TypeScript`
```typescript=
class Car {
make: string
model: string
year: number
isElectric: boolean
}
class Truck {
make: string
model: string
year: number
towingCapacity: number
}
const vehicle = {
make: "Honda",
model: "Accord",
year: 2017,
}
function printCar(car: {
make: string
model: string
year: number
}) {
console.log(`${car.make} ${car.model} (${car.year})`)
}
printCar(new Car()) // Fine
printCar(new Truck()) // Fine
printCar(vehicle) // Fine
```
### Duck Typing
*Duck typing gets its name from the "duck test"*
"Duck typing" is usually used to describe dynamic type systems.
## Union and Intersection
Union types can be described using the `|` (pipe) operator
```typescript=
function flipCoin(): "heads" | "tails" {
if (Math.random() > 0.5) return "heads";
return "tails";
}
const outcome = flipCoin();
```
```typescript=
function flipCoin(): "heads" | "tails" {
if (Math.random() > 0.5) return "heads"
return "tails"
}
function maybeGetUserInfo():
| ["error", Error]
| ["success", { name: string; email: string }] {
if (flipCoin() === "heads") {
return [
"success",
{ name: "Mike North", email: "mike@example.com" },
];
} else {
return [
"error",
new Error("The coin landed on TAILS :("),
];
}
}
const outcome = maybeGetUserInfo();
```
### Narrowing with type guards
Type guards are expressions, which when used with control flow statement,
allow us to have a more specific type for a particular value.
```typescript=
const outcome = maybeGetUserInfo();
const [first, second] = outcome;
if (second instanceof Error) {
// In this branch of your code, second is an Error
second
} else {
// In this branch of your code, second is the user info
second
}
```
### Discriminated Unions
```typescript=
const outcome = maybeGetUserInfo();
if (outcome[0] === "error") {
// In this branch of your code, second is an Error
outcome
} else {
// In this branch of your code, second is the user info
outcome
}
```
### Intersection Types
Intersection types in TypeScript can be described using the `&` (ampersand) operator.
```typescript=
function makeWeek(): Date & { end: Date } {
const start = new Date();
const end = new Date(start.valueOf() + ONE_WEEK);
return { ...start, end };
}
const thisWeek = makeWeek();
thisWeek.toISOString();
thisWeek.end.toISOString();
```
## Interfaces and Type Aliases
### Type Aliases
- define a more meaningful name for the type
- declare the particulars of the type in a single place
- import and export this type from modules
Using `TitleCase` to format the alias' name.
```typescript=
export type UserContactInfo = {
name: string,
email: string
}
```
We can only declare an alias of a given name once within a given scope.
```typescript=
type UserContactInfo = {
}
// Error
type UserContactInfo = {
}
```
```typescript=
type UserInfoOutcomeError = ["error", Error]
type UserInfoOutcomeSuccess = [
"success",
{ name: string; email: string }
]
type UserInfoOutcome =
| UserInfoOutcomeError
| UserInfoOutcomeSuccess
export function maybeGetUserInfo(): UserInfoOutcome {
// implementation is the same in both examples
if (Math.random() > 0.5) {
return [
"success",
{ name: "Mike North", email: "mike@example.com" },
]
} else {
return [
"error",
new Error("The coin landed on TAILS :("),
]
}
}
```
### Inheritance
create type aliases that combine existing types with new behaviour
by using Intersection (&) types.
```typescript=
type SpecialDate = Date & { getReason(): string }
const newYearsEve: SpecialDate = {
...new Date(),
getReason: () => "Last day of the year",
}
newYearsEve.getReason
```
### Interfaces
An interface is a way of defining an object type.
An "object type" can be thought of as,
"an instance of a class could conceivably look like this".
```typescript=
interface UserInfo {
name: string
email: string
}
```
### Inheritance
**EXTENDS**
```typescript=
interface Animal {
isAlive(): boolean
}
interface Mammal extends Animal {
getFurOrHairColor(): string
}
interface Dog extends Mammal {
getBreed(): string
}
function careForDog(dog: Dog) {
dog.getBreed
}
```
**IMPLEMENTS**
```typescript=
class LivingOrganism {
isAlive() {
return true
}
}
interface AnimalLike {
eat(food): void
}
interface CanBark {
bark(): string
}
class Dog
extends LivingOrganism
implements AnimalLike, CanBark
{
bark() {
return "woof"
}
eat(food) {
consumeFood(food)
}
}
```
### Open Interfaces
```typescript=
interface AnimalLike {
isAlive(): boolean
}
function feed(animal: AnimalLike) {
animal.eat
animal.isAlive
}
interface AnimalLike {
eat(food): void
}
```
```typescript=
window.document // an existing property
window.exampleProperty = 42
interface Window {
exampleProperty: number
}
```
### Recursion Types
```typescript=
type NestedNumbers = number | NestedNumbers[]
const val: NestedNumbers = [3, 4, [5, 6, [7], 59], 221];
```
## Functions
### Callable Types
Both type aliases and interfaces offer the capability to describe call signatures
```typescript=
interface TwoNumberCalculation {
(x: number, y: number): number;
}
type TwoNumberCalc = (x: number, y: number) => number;
const add: TwoNumberCalculation = (a, b) => a + b;
const subtract: TwoNumberCalc = (x, y) => x - y;
```
### Function overloads
```typescript=
type FormSubmitHandler = (data: FormData) => void
type MessageHandler = (evt: MessageEvent) => void
function handleMainEvent(
elem: HTMLFormElement,
handler: FormSubmitHandler
)
function handleMainEvent(
elem: HTMLIFrameElement,
handler: MessageHandler
)
function handleMainEvent(
elem: HTMLFormElement | HTMLIFrameElement,
handler: FormSubmitHandler | MessageHandler
) {}
const myFrame = document.getElementsByTagName("iframe")[0]
const myForm = document.getElementsByTagName("form")[0]
handleMainEvent(myFrame, (val) => {
})
handleMainEvent(myForm, (val) => {
})
```
### `this` types
```typescript=
function myClickHandler(
this: HTMLButtonElement,
event: Event
) {
this.disabled = true
}
```
## Classes
### Class Fields
```typescript=
class Car {
make: string
model: string
year: number
constructor(make: string, model: string, year: number) {
this.make = make
this.model = model
this.year = year
}
}
```
### Access modifier keywords
| keyword | who can access |
| ---------- | --------------------------------------- |
| public | everyone (this is the default) |
| protected | the instance itself, and subclasses |
| private | only the instance itself |
```typescript=
class Car {
public make: string
public model: string
public year: number
protected vinNumber = generateVinNumber()
private doorLockCode = generateDoorLockCode()
constructor(make: string, model: string, year: number) {
this.make = make
this.model = model
this.year = year
}
protected unlockAllDoors() {
unlockCar(this, this.doorLockCode)
}
}
class Sedan extends Car {
constructor(make: string, model: string, year: number) {
super(make, model, year)
this.vinNumber
this.doorLockCode
}
public unlock() {
console.log("Unlocking at " + new Date().toISOString())
this.unlockAllDoors()
}
}
```
### Private
```typescript=
class Car {
public make: string
public model: string
#year: number
constructor(make: string, model: string, year: number) {
this.make = make
this.model = model
this.#year = year
}
}
```
### Readonly
```typescript=
class Car {
public make: string
public model: string
public readonly year: number
constructor(make: string, model: string, year: number) {
this.make = make
this.model = model
this.year = year
}
updateYear() {
this.year++
}
}
```
### Param properties
```typescript=
class Car {
constructor(
public make: string,
public model: string,
public year: number
) {}
}
```
## Top and Bottom Types
### Types describe sets of allowed values
`{true, false}`.
```typescript=
const x: boolean
```
`{y | y is a number}`
```typescript=
const y: number
```
### Top Types
A top type is a type that describes
**any possible value allowed by the system**
`{x | x could be anything}`
TypeScript provides two of these types: `any` and `unknown`
#### any
playing by the usual JavaScript rules
It's important to understand that `any` is not necessarily a problem.
sometimes it's exactly the right type to use for a particular situation.
We can see here that `any`
is not always a "bug" or a "problem"
it just indicates maximal flexibility
and the absence of type checking validation.
#### unknown
Like `any`, unknown can accept any value
However, `unknown` is different from `any`
in a very important way:
**Values with an `unknown` type cannot be used without first applying a type guard**
```typescript=
let myUnknown: unknown = 14
// This code runs for { myUnknown| anything }
if (typeof myUnknown === "string") {
// This code runs for { myUnknown| all strings }
console.log(myUnknown, "is a string")
} else if (typeof myUnknown === "number") {
// This code runs for { myUnknown| all numbers }
console.log(myUnknown, "is a number")
} else {
}
```
#### Practical use of top types
if you convert a project from JavaScript to TypeScript
A lot of things will be `any` until you get a chance
to give them some attention.
`unknown` is great for values received at runtime.
By consumers of these values
to perform some light validation before using them,
errors are caught earlier,
and can often be surfaced with more context.
### Bottom type: `never`
A bottom type is a type that describes
**no possible value allowed by the system**
“any value from the following set: { } (intentionally empty)”
```typescript=
class Car {
drive() {
console.log("vroom")
}
}
class Truck {
tow() {
console.log("dragging something")
}
}
type Vehicle = Truck | Car
let myVehicle: Vehicle = obtainRandomVehicle()
// The exhaustive conditional
if (myVehicle instanceof Truck) {
myVehicle.tow() // Truck
} else if (myVehicle instanceof Car) {
myVehicle.drive() // Car
} else {
// NEITHER!
const neverValue: never = myVehicle
}
```
**error subclass**
```typescript=
class UnreachableError extends Error {
constructor(_nvr: never, message: string) {
super(message)
}
}
// The exhaustive conditional
if (myVehicle instanceof Truck) {
myVehicle.tow() // Truck
} else if (myVehicle instanceof Car) {
myVehicle.drive() // Car
} else {
// NEITHER!
throw new UnreachableError(
myVehicle,
`Unexpected vehicle type: ${myVehicle}`
)
}
```
## Type Guards and narrowing
### Built-in type guards
```typescript=
let value:
| Date
| null
| undefined
| "pineapple"
| [number]
| { dateRange: [Date, Date] }
// instanceof
if (value instanceof Date) {
value // Date
}
// typeof
else if (typeof value === "string") {
value // "pineapple"
}
// Specific value check
else if (value === null) {
value // null
}
// Truthy/falsy check
else if (!value) {
value // undefined
}
// Some built-in functions
else if (Array.isArray(value)) {
value // [number]
}
// Property presence check
else if ("dateRange" in value) {
value // { dateRange: [Date, Date]; }
} else {
value // never
}
```
### User-defined type guards
**“Untruths” in your type guards will propagate quickly through your codebase and cause problems that are quite difficult to solve.**
```typescript=
interface CarLike {
make: string
model: string
year: number
}
let maybeCar: unknown
// the guard
function isCarLike(
valueToTest: any
): valueToTest is CarLike {
return (
valueToTest &&
typeof valueToTest === "object" &&
"make" in valueToTest &&
typeof valueToTest["make"] === "string" &&
"model" in valueToTest &&
typeof valueToTest["model"] === "string" &&
"year" in valueToTest &&
typeof valueToTest["year"] === "number"
)
}
// using the guard
if (isCarLike(maybeCar)) {
maybeCar
}
```
#### Asserts value is Type
```typescript=
interface CarLike {
make: string
model: string
year: number
}
let maybeCar: unknown
// the guard
function assertsIsCarLike(
valueToTest: any
): asserts valueToTest is CarLike {
if (
!(
valueToTest &&
typeof valueToTest === "object" &&
"make" in valueToTest &&
typeof valueToTest["make"] === "string" &&
"model" in valueToTest &&
typeof valueToTest["model"] === "string" &&
"year" in valueToTest &&
typeof valueToTest["year"] === "number"
)
)
throw new Error(
`Value does not appear to be a CarLike${valueToTest}`
)
}
```
## Nullish
### `null`
there is a value, and that value is nothing.
### `undefined`
the value isn't available (yet)
### `void`
a function's return value should be ignored
### Non-null assertion operator
The non-null assertion operator (`!.`)
is used to cast away the possibility
that a value might be `null` or `undefined`.
```typescript=
type GroceryCart = {
fruits?: { name: string; qty: number }[]
vegetables?: { name: string; qty: number }[]
}
const cart: GroceryCart = {}
cart.fruits.push({ name: "kumkuat", qty: 1 })
cart.fruits!.push({ name: "kumkuat", qty: 1 })
```
### Definite assignment operator
```typescript=
class ThingWithAsyncSetup {
setupPromise: Promise<any>
isSetup!: boolean
constructor() {
this.setupPromise = new Promise((resolve) => {
this.isSetup = false
return this.doSetup()
}).then(() => {
this.isSetup = true
})
}
private async doSetup() {
}
}
```
## Generics
`Generics` allow us to parameterize types
What we need here is some mechanism of defining a relationship between the type of the thing we’re passed, and the type of the thing we’ll return. This is what Generics are all about
```typescript=
function listToDict(
list: any[],
idGen: (arg: any) => string
): { [k: string]: any } {
const dict: { [k: string]: any } = {}
list.forEach((element) => {
const dictKey = idGen(element)
dict[dictKey] = element
})
return dict
}
```
### Defining a type parameter
Type parameters can be thought of as “function arguments, but for types”.
Generics may change their type, depending on the type parameters you use with them.
```typescript=
function listToDict<T>(
list: T[],
idGen: (arg: T) => string
): { [k: string]: T } {
const dict: { [k: string]: T } = {}
return dict
}
```
### Generic Constraints
Generic constraints allow us to describe the “minimum requirement” for a type param
```typescript=
function listToDict<T extends HasId>(list: T[]): Dict<T> {
}
```
### Scopes and TypeParams
```typescript=
// outer function
function tupleCreator<T>(first: T) {
// inner function
return function finish<S>(last: S): [T, S] {
return [first, last]
}
}
```
<style>
.markdown-body pre {
background-color: #1E1E1E;
border: 1px solid #555 !important;
color: #73BCE0;
border-radius:8px;
/*border-radius:0px;*/
}
.markdown-body pre
.htmlembedded,
.markdown-body pre
.diff {
color: #C8D4C8 !important;
}
.markdown-body pre
.hljs-tag {
color: #6D726E;
}
.markdown-body pre
.token.keyword {
color: #C586C0;
}
.markdown-body pre
.token.string {
color: #C68362;
}
.markdown-body pre
.hljs-string {
color: #C68362;
}
.markdown-body pre
.hljs-comment,
.markdown-body pre
.token.comment {
color: #6A9955;
}
.markdown-body pre
.hljs-attr {
color: #73BCE0;
}
.markdown-body pre
.hljs-name {
color:#569CD6;
}
.markdown-body pre
.token.operator {
color:#C8D4C8;
background:transparent;
}
.markdown-body pre
.token.property {
color: #73BCE0;
}
.markdown-body pre
.token.function {
color: #DCDCAA;
}
.markdown-body pre
.token.builtin {
color: #34B294;
}
.markdown-body pre
.token.number {
color: #B5CEA8;
}
.markdown-body pre
.token.constant {
color: #3BC1FF;
}
.markdown-body pre
.hljs-addition {
color: #96D47D;
background: #373D29;
}
.markdown-body pre
.hljs-deletion {
color: #E76A6A;
background: #4B1818;
}
</style>