Skip to content

Commit

Permalink
Intermediate TypeScript v2 notes (#913)
Browse files Browse the repository at this point in the history
* notes: nullish values

* notes: modules and cjs interop

* notes: fixes to modules and cjs interop

* WIP

* notes: generic scopes and constraints

* notes: generic scopes and constraints - updated

* WIP

* WIP

* notes: conditional types

* notes: infer

* notes: mapped types

* WIP

* fix lint error
  • Loading branch information
mike-north authored Oct 23, 2023
1 parent fa23cc2 commit 3b6cd18
Show file tree
Hide file tree
Showing 29 changed files with 1,178 additions and 118 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,8 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
117 changes: 117 additions & 0 deletions packages/notes-intermediate-ts-v2/src/04-nullish-values.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//* Null

const userInfo = {
name: 'Mike',
email: '[email protected]',
secondaryEmail: null, // user has no secondary email
}

//* Undefined
/*
// interface FormInProgress {
// createdAt: Date
// data: FormData
// completedAt?: Date
// }
// const formInProgress: FormInProgress = {
// createdAt: new Date(),
// data: new FormData(),
// }
// function submitForm() {
// const myDate: Date = formInProgress.completedAt
// formInProgress.completedAt = new Date()
// }
//* void
/*
// console.log(`console.log returns nothing.`)
//* Non-null assertion operator
/*
// 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 assertion
/*
// class ThingWithAsyncSetup {
// setupPromise: Promise<any>
// isSetup: boolean
// constructor() {
// this.setupPromise = new Promise((resolve) => {
// this.isSetup = false
// return this.doSetup(resolve)
// }).then(() => {
// this.isSetup = true
// })
// }
// private async doSetup(resolve: (value: unknown) => void) {
// // some async stuff
// }
// }
//* Optional Chaining
/*
// type Payment = {
// id: string
// amount: number
// createdAt: Date
// }
// type Invoice = {
// id: string
// due: number
// payments: Payment[]
// lastPayment?: Payment
// createdAt: Date
// }
// type Customer = {
// id: string
// lastInvoice?: Invoice
// invoices: Invoice[]
// }
// type ResponseData = {
// customers?: Customer[]
// customer?: Customer
// }
// function getLastPayment(data: ResponseData): number | undefined {
// const { customer } = data
// if (!customer) return
// const { lastInvoice } = customer
// if (!lastInvoice) return
// const { lastPayment } = lastInvoice
// if (!lastPayment) return
// return lastPayment.amount
// }
/*
// function getLastPayment2(data: ResponseData): number | undefined {
// return data?.customer?.lastInvoice?.lastPayment?.amount
// }
//* Nullish Coalescing
/*
// function setVolume(v: number): void {}
// type PlayerConfig = {
// volume?: 0 | 25 | 50 | 75 | 100
// }
// function initializePlayer(config: PlayerConfig): void {
// const vol =
// typeof config.volume === 'undefined' ? 50 : config.volume
// setVolume(vol)
// }
/**/
export default {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Banana {
peel() {}
}

module.exports = { Banana }
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export abstract class BerryBase {
abstract color: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { BerryBase } from "./berry-base";

export class Blueberry extends BerryBase{
readonly color: 'blue';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './blueberry';
export * from './strawberry';
export * from './raspberry';
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { BerryBase } from './berry-base'

export class Raspberry extends BerryBase {
constructor(public readonly color: 'red' | 'black') {
super()
}
}

export function pickRaspberries(time: number): Raspberry[] {
const berries: Raspberry[] = []
console.log('picking raspberries')
const numPicked = Math.round(Math.random() * 15)
while (berries.length < numPicked) {
berries.push(new Raspberry(Math.random() > 0.5 ? 'red' : 'black'))
}
return berries
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { BerryBase } from "./berry-base";

export class Strawberry extends BerryBase{
readonly color: 'red';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export function lemon () {}
export function lime () {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Global types
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//* ES module imports and exports

/*
// //? named imports
// import { Blueberry, Raspberry } from './berries'
// import Kiwi from './kiwi' // default import
// export function makeFruitSalad() {} // named export
// export default class FruitBasket {} // default export
// export { lemon, lime } from './citrus' // re-export
// export * as berries from './berries' // re-export entire module as a single namespace
/*
// //? namespace import
// import * as allBerries from './berries' // namespace import
// allBerries.Strawberry // using the namespace
// allBerries.Blueberry
// allBerries.Raspberry
// export * from './berries' // namespace re-export
//* Importing types
/*
// let x: Raspberry = { color: 'red' }
// const y = new Raspberry('red')
//* Type-only imports
/*
// import type { Strawberry } from './berries/strawberry'
// let z: Strawberry = { color: 'red' }
// new Strawberry()
//* CommonJS Interop
/*
// //? "import as a namespace"
// import * as bananaNamespace from './banana'
// const banana = new bananaNamespace.Banana()
/*
//? import as a single thing (rare)
// import * as melonNamespace from './melon'
//? special ts import
// import Melon = require('./melon')
// const melon = new Melon()
// melon.cutIntoSlices()
//* Native ES Module support
/*
// import * as bananaNamespace from './banana.cjs'
// package.json --> 'type: module', 'type: commonjs'
//* Importing non-ts things
/*
// import img from "./ts-logo.png"
//? Add to global.d.ts
// declare module '*.png' {
// const imgUrl: string
// export default imgUrl
// }
/**/
export default {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default class Kiwi {
color = 'green';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Melon {
cutIntoSlices() { }
}

module.exports = Melon
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//* Generic Constraints - A motivating use case
const phoneList = [
{ customerId: '0001', areaCode: '321', num: '123-4566' },
{ customerId: '0002', areaCode: '174', num: '142-3626' },
{ customerId: '0003', areaCode: '192', num: '012-7190' },
{ customerId: '0005', areaCode: '402', num: '652-5782' },
{ customerId: '0004', areaCode: '301', num: '184-8501' },
]
const phoneDict = {
'0001': {
customerId: '0001',
areaCode: '321',
num: '123-4566',
},
'0002': {
customerId: '0002',
areaCode: '174',
num: '142-3626',
},
/*... and so on */
}

function listToDict<T>(
list: T[], // array as input
idGen: (arg: T) => string, // fn for obtaining item's id
): { [k: string]: T } {
// create dict to fill
const dict: { [k: string]: T } = {}

for (let item of list) {
// for each item
dict[idGen(item)] = item // make a key store in dict
}

return dict // result
}
/*
// interface HasId {
// id: string
// }
// interface Dict<T> {
// [k: string]: T
// }
// function listToDict(list: HasId[]): Dict<HasId> {
// const dict: Dict<HasId> = {}
// list.forEach((item) => {
// dict[item.id] = item
// })
// return dict
// }
/*
//? Let's make it
// function listToDict<T>(list: T[]): Dict<T> {
//* Describing the constraint
/*
// function listToDict<T extends HasId>(list: T[]): Dict<T> {
//* Scopes and Type Parameters
/*
// function eatApple(bowl: any, eater: (arg: any) => void) {}
// function receiveFruitBasket(bowl: any) {
// console.log('Thanks for the fruit basket!')
// // only `bowl` can be accessed here
// eatApple(bowl, (apple: any) => {
// // both `bowl` and `apple` can be accessed here
// })
// }
// // outer function
// function tupleCreator<T>(first: T) {
// // inner function
// return function finish<S>(last: S): [T, S] {
// return [first, last]
// }
// }
// const finishTuple = tupleCreator(3 as const)
// const t1 = finishTuple(null)
// const t2 = finishTuple([4, 8, 15, 16, 23, 42])
//* Best practices
// interface HasId {
// id: string
// }
// interface Dict<T> {
// [k: string]: T
// }
// function example1<T extends HasId[]>(list: T) {
// return list.pop()
// // ^?
// }
// function example2<T extends HasId>(list: T[]) {
// return list.pop()
// // ^?
// }
// class Payment implements HasId {
// static #next_id_counter = 1;
// id = `pmnt_${Payment.#next_id_counter++}`
// }
// class Invoice implements HasId {
// static #next_id_counter = 1;
// id = `invc_${Invoice.#next_id_counter++}`
// }
// const result1 = example1([
// // ^?
// new Payment(),
// new Invoice(),
// new Payment()
// ])
// const result2 = example2([
// // ^?
// new Payment(),
// new Invoice(),
// new Payment()
// ])
/**/
export default {}
Loading

0 comments on commit 3b6cd18

Please sign in to comment.