Luca Del Puppo - Senior Software Developer
Image by catalyststuff on Freepik
I love types and improving the JavaScript codebases
Not all are happy about me
I’m here to simplify your job and prevent mistakes
JavaScript is my friend and I always
support it
Or maybe not
Image by catalyststuff on Freepik
Luca Del Puppo
Love sport: running, hiking
Love animals
function main(name) {
console.log(`Hello from ${name}`)
}
function main(name: string): void {
console.log(`Hello from ${name}`)
}
TypeScript Code
Compilation/Transpiling
Vanilla JavaScript
TypeScript File (*.ts)
Classes, Functions Types ..
TypeScript Compiler (tsc)
Target: ES3, ES6, ES7...
JavaScript File (*.js)
Runs Everywhere
Big Projects
Many Developers
Shared Libraries
Strong type applications
Dynamic objects (but real dynamic, ehm...)
Small libraries with performance purposes
Side project in your own room *
* but that depends on you 😅
let n: number = 1
let s: string = 'hello'
let isDone: boolean = false
let b: boolean = true
let o: object = { foo: 'bar' }
let a: string[] = ['hello', 'world']
let a2: Array<string> = ['hello', 'world']
let f: Function = function (x: number, y: number): number {
return x + y
}
let f2: (x: number, y: number) => number =
(x, y) => x + y
let sy = Symbol('key')
let n: null = null
let u: undefined = undefined
let o: {foo?: string} = {foo: undefined}
Image by catalyststuff on Freepik
Image by catalyststuff on Freepik
let myAny: any = true
myAny.trim().reverse().split(',');
let myAnyNumeric: number = myAny;
const sum = myAnyNumeric + 3;
Good luck in production
let myUnknown: unknown = true;
myUnknown.trim();
let myAnyNumeric: number = myUnknown;
if (typeof myUnknown === 'string') {
myUnknown.trim();
}
if (typeof myUnknown === "number") {
let myAnyNumeric: number = myUnknown;
const sum = myAnyNumeric + 3;
}
'myUnknown' is of type 'unknown'.ts(18046)
❌
Type 'unknown' is not assignable to type 'number'.ts(2322)
❌
type Square = {
size: number,
}
type Rectangle = {
width: number,
height: number,
}
type Circle = {
radius: number,
}
interface Square {
size: number,
}
interface Rectangle {
width: number,
height: number,
}
interface Circle {
radius: number,
}
interface Rectangle {
width: number,
}
interface Rectangle {
height: number,
}
let rect: Rectangle = {
width: 100,
height: 200,
}
interface Rectangle {
width: number,
height: number,
}
Merge
Member Visibility
public, protected, private, #
Method
Getter/Setter
Static
Implements/Extends
class Shape {
private square: Square;
constructor(square: Square, public rectangle: Rectangle, private circle: Circle) {
this.square = square;
}
getSquare(): Square {
return this.square;
}
getRectangle(): Rectangle {
return this.rectangle;
}
getCircle(): Circle {
return this.circle;
}
}
let square: Square = { size: 10 };
let rectangle: Rectangle = { width: 10, height: 20 };
let circle: Circle = { radius: 5 };
let shape = new Shape(square, rectangle, circle);
type Ingredient = string
type PizzaIngredient = 'flour' | 'mozzarella'
| 'tomato' | 'pepperoni'
Image by catalyststuff on Freepik
const ingredientOne: PizzaIngredient = 'flour'
const ingredientTwo: PizzaIngredient = 'tomato'
const ingredientThree: PizzaIngredient = 'mozzare'
Type '"mozzare"' is not assignable to type 'PizzaIngredient'.ts(2322)
type Name = 'John' | 'Jane' | 'Jack';
type Surname = 'Doe' | 'Dane' | 'Black';
type FullName = `${Name} ${Surname}`;
let JohnDoe: FullName = 'John Doe';
let JaneDane: FullName = 'Jane Dane';
let JackBlack: FullName = 'Jack Black';
let who: FullName = 'John Blue';
Type '"John Blue"' is not assignable to type
'"John Doe" | "John Dane" | "John Black" | "Jane Doe"
| "Jane Dane" | "Jane Black" | "Jack Doe" | "Jack Dane"
| "Jack Black"'.ts(2322)
❌
type Point1D = { x: number }
type Point2D = Point1D & { y: number }
type Point3D = Point2D & { z: number }
Image by catalyststuff on Freepik
type Dish = 'Pizza' | 'Pineapple'
function order (dish: Dish) {
switch (dish) {
case 'Pizza':
return '🍕'
case 'Pineapple':
return '🍍'
}
}
type Dish = 'pizza' | 'pasta' | 'sandwich'
function doOrder(dish: Dish) {
switch (dish) {
case 'pizza':
return '🍕'
case 'pasta':
return '🍝'
default:
let unknownDish: never = dish;
throw new Error(`Dish ${unknownDish} doesn't exists`)
}
}
Type 'string' is not assignable
to type 'never'.(2322)
❌
function assert(conditional: unknown, message: string):
asserts conditional {
if (!conditional) {
throw new Error(message);
}
}
assert({}, 'Should be defined')
assert(10, 'Should be defined')
assert(undefined, 'Should be defined')
// Error: Should be defined
Image by catalyststuff on Freepik
type Person = {name: string;};
let person: Person | undefined;
function assertIsDefined(person: undefined | Person): asserts person is Person {
if (person === undefined) {
throw new Error("Person should be defined");
}
}
function printPersonWithoutCheck() {
console.log(person.name);
// 'person' is possibly 'undefined'.ts(18048)
}
function printPersonWithCheck() {
assertIsDefined(person);
console.log(person.name);
}
type Square = { kind: 'square', size: number }
type Circle = { kind: 'circle', radius: number }
type Shape = Square | Circle
function isSquare(shape: Shape): shape is Square {
return shape.kind === 'square'
}
function isCircle(shape: Shape): shape is Circle {
return shape.kind === 'circle'
}
function area(shape: Shape): number {
if (isSquare(shape))
return shape.size * shape.size
if (isCircle(shape))
return Math.PI * shape.radius ** 2
}
Image by catalyststuff on Freepik
type Apple = { kind: 'fruit' }
type Strawberry = { kind: 'fruit' }
type Car = { kind: 'vehicle' }
type Truck = { kind: 'vehicle' }
type Tomato = { kind: 'vegetable' }
type Cucumber = { kind: 'vegetable' }
type AllTogether = Apple | Strawberry | Car | Truck | Tomato | Cucumber
type GetFruits<T> = T extends { kind: 'fruit' } ? T : never
type GetVehicles<T> = T extends { kind: 'vehicle' } ? T : never
type GetVegetables<T> = T extends { kind: 'vegetable' } ? T : never
type Fruits = GetFruits<AllTogether> // Apple | Strawberry
type Vehicles = GetVehicles<AllTogether> // Car | Truck
type Vegetables = GetVegetables<AllTogether> // Tomato | Cucumber
Image by catalyststuff on Freepik
type Obj = {
[key: string]: unknown
}
let obj: Obj = {
name: 'Jack',
age: 18
}
type Obj = Record<string, unknown>
Mapped Types
type Person = {
name: string;
surname: string;
age: number;
}
type PersonKeys = keyof Person;
// 'name' | 'surname' | 'age'
type PersonTypes = Person[keyof Person];
// string | number
type Person = {
readonly name: string
readonly surname: string
}
let obj: Person = {
name: 'Jack',
surname: 'Sparrow',
}
obj.name = 'John'
// Index signature in type 'Obj'
// only permits reading.ts(2542)
type PersonReadonly = Readonly<{
name: string
surname: string
}>
type Readonly<T> = {
+readonly [K in keyof T]: T[K]
}
const person = {
name: 'John',
surname: 'Doe',
age: 30
} as const;
type Person = {
readonly name: string
readonly surname: string
}
type NoReadonly<T> = {
-readonly [K in keyof T]: T[K]
}
type NoReadonlyPerson = NoReadonly<Person>
let obj: NoReadonlyObj = {
name: 'Jack',
surname: 'Sparrow',
}
obj.name = 'John'
type Person = {
name?: string
surname?: string
}
let obj: Person = {
name: 'Jack',
}
type Person = Partial<{
name: string
surname: string
}>
type Partial<T> = {
[K in keyof T]+?: T[K]
}
type Person = {
name: string
surname: string
}
let obj: Person = {
name: 'Jack',
surname: 'Black'
}
type Obj = Required<{
name?: string
surname?: string
}>
type Required<T> = {
[K in keyof T]-?: T[K]
}
TypeScript Tips
TypeScript Documentations
import { createError } from '@middy/util'
const wsRouteHandler = (routes) => {
const routesStatic = {}
for (const route of routes) {
const { routeKey, handler } = route
// Static
routesStatic[routeKey] = handler
}
return (event, context, abort) => {
const { routeKey } = event.requestContext ?? {}
if (!routeKey) {
throw new Error('[ws-router] Unknown ws event format')
}
// Static
const handler = routesStatic[routeKey]
if (typeof handler !== 'undefined') {
return handler(event, context, abort)
}
// Not Found
throw createError(404, 'Route does not exist')
}
}
export default wsRouteHandler
import middy from '@middy/core'
import { APIGatewayProxyWebsocketHandlerV2 } from 'aws-lambda'
interface Route<T = never> {
routeKey: string
handler: APIGatewayProxyWebsocketHandlerV2<T>
}
declare function wsRouterHandler (routes: Route[]): middy.MiddyfiedHandler
export default wsRouterHandler
index.d.ts
{
...
"main": "./index.cjs",
"module": "./index.js",
"exports": {
".": {
"import": {
"types": "./index.d.ts",
"default": "./index.js"
},
"require": {
"types": "./index.d.ts",
"default": "./index.cjs"
}
}
},
"types": "index.d.ts",
"files": [
"index.js",
"index.cjs",
"index.d.ts"
],
...
}
import middy from '@middy/core'
import { APIGatewayProxyWebsocketHandlerV2 } from 'aws-lambda'
import { expectType } from 'tsd'
import wsRouterHandler from '.'
const connectLambdaHandler: APIGatewayProxyWebsocketHandlerV2 = async () => {
return {
statusCode: 200,
body: 'Connected to websocket'
}
}
const middleware = wsRouterHandler([
{
routeKey: '$connect',
handler: connectLambdaHandler
}
])
expectType<middy.MiddyfiedHandler>(middleware)
Image by catalyststuff on Freepik
TypeScript Tips
Typescript - Tips & Tricks
@puppo92
Luca Del Puppo
Puppo_92
@puppo