1. สูตรโกงเขียนโค้ด
  2. TypeScript

⚙️ tsconfig.json

OptionDescription
targetตั้งค่าเป้าหมายที่จะ Compiler .js ออกมารูปแบบใด เช่น es6 es2023
moduleกำหนด Module System ที่จะใช้งาน เช่น commonjs es6 es2022
libกำหนด Built-in API ที่จะใช้งาน เช่น es6 dom
moduleResolutionกำหนดรูปแบบ Module ที่จะใช้งาน เช่น node16 classic
outDirตั้งค่า Output Directory เมื่อทำการ Build
rootDirตั้งค่า Root Directory ของโปรเจกต์
pathsกำหนด Alias สำหรับ Import Module (มีประโยชน์สำหรับโปรเจกต์ขนาดใหญ่)
sourceMapเปิดการใช้ Source Map (แนะนำ false เพราะช่วยปิดการค้นดู Source Code)
esModuleInteropกำหนดให้ CommonJS ทำงานเหมือน ES Module (แนะนำ true)
skipLibCheckข้ามการตรวจสอบ Type ของไฟล์ในโฟลเดอร์ node_modules (แนะนำ true เพื่อเพิ่มความเร็วในการ Compile)
resolveJsonModuleกำหนดให้ TypeScript สามารถ Import ไฟล์ JSON ได้ (แนะนำ true)
strictตั้งค่าการใช้งาน Strict Mode (แนะนำ true)
noImplicitAnyปิดการใช้งาน Implicit any (แนะนำกับบางโปรเจกต์ให้เป็น true)
strictNullChecksตั้งค่าตรวจสอบข้อมูลที่อาจจะเป็น Null (แนะนำ true)
noUnusedLocalsห้ามมี Variable ที่ไม่ใช้งาน (แนะนำ true)
noUnusedParametersห้ามมี Parameters ที่ไม่ใช้งาน (แนะนำ true)
includeกำหนดรายชื่อไฟล์หรือโฟลเดอร์ที่จะทำการ Compile (แนะนำ ["src/**/*"])
excludeกำหนดรายชื่อไฟล์หรือโฟลเดอร์ที่จะไม่ทำการ Compile (แนะนำ ["node_modules", "dist"])

Utility Types

CodeDescription
Partial<T>Constructs a type with all properties of T set to optional.
Required<T>Constructs a type with all properties of T set to required.
Readonly<T>Constructs a type with all properties of T set to readonly.
Record<K, T>Constructs an object type whose keys are K and values are T.
Pick<T, K>Constructs a type by picking a set of properties K from T.
Omit<T, K>Constructs a type by omitting a set of properties K from T.
Exclude<T, U>Excludes types from T that are assignable to U.
Extract<T, U>Extracts types from T that are assignable to U.
NonNullable<T>Excludes null and undefined from T.
ReturnType<T>Extracts the return type of a function type T.
InstanceType<T>Extracts the instance type of a constructor function type T.

DefinitelyTyped

pnpm i -D @types/node
pnpm i -D @types/lodash
pnpm i -D @types/react
"compilerOptions": {
  // ...
  "baseUrl": "./src", // กำหนดสถานที่ของ Source ทั้งหมด
  "paths": {
    "@/*": ["./*"]    // กำหนด Path แบบ Re-map ใหม่ ทำให้ใช้ Path ได้ตรงกัน
  },
  "allowJs": true,    // อนุญาตให้ใช้ .js (ไม่แนะนำ แต่ใช้กรณีที่โปรเจกต์เรามีไฟล์นี้)
  "outDir": "./dist", // กำหนด Output Directory (ควรกำหนดหากเรา allowJs)
}
// ./types/any-file-name.d.ts
declare module 'the-module-name' {
  export const someConstant: number
  export function someFunction(someArgs: string[]): Promise<string>
}

Type Annotation

const data: any = ['whatever', 123, true, [false, '123'], { date: new Date() }]
const name: string = 'John Doe'
const age: number = 18
const active: boolean = true
const birthDate: Date = new Date('1998-01-01')
const classrooms: string[] = ['A1', 'B2', 'C3']
const subjectList: { subject: string; score: number }[] = [
  { subject: 'Math', score: 80 },
  { subject: 'English', score: 75 },
]

const getSubjectScore1: ((subject: string) => number) = (subject) => {
  return subjectList.find((item) => item.subject === subject)?.score ?? -1
}
function getSubjectScore2(subject: string): number {
  return subjectList.find((item) => item.subject === subject)?.score ?? -1
}
function showName(name: string): void {
  console.log(name)
}

Type Aliases

type UserName = string
type UserAge = number
const name1: UserName = 'John Doe'
const name2: UserName = 'Jane Doe'
const age1: UserAge = 18
const age2: UserAge = 19

Interface

interface User {
  id: number
  name: string
  role: 'admin' | 'member'
}
const user: User = { id: 1, name: 'John', role: 'member' }

Type Composing

// Union
const bill1: number | number[] = 390
const bill2: number | number[] = [290, 250, 300, 1290]
const bill3: (number | string)[] = [290, 250, 300, '1,290']

// Intersection (Composing)
type ProductInfo = { name: string; price: number; }
type ProductComputerDetail = { os: 'Windows' | 'macOS'; cpu: string; ram: number; }
type ProductSmartphoneDetail = { os: 'Android' | 'iOS', cpu: string; ram: number; }
type Computer = ProductInfo & ProductComputerDetail
type Smartphone = ProductInfo & ProductSmartphoneDetail
const pc: Computer = { name: 'Good Laptop', price: 29000, os: 'Windows', cpu: 'Intel', ram: 8 }
const mac: Computer = { name: 'Great Laptop', price: 39000, os: 'macOS', cpu: 'Apple', ram: 16 }
const phone: Smartphone = { name: 'iPhone 13', price: 26000, os: 'iOS', cpu: 'Apple', ram: 8 }

// Tuple
const coordinate: [number, number] = [13.918840204972751, 100.60899506117163]

String Literal

type Position = 'left' | 'right' | 'center' | undefined
const position1: Position = 'left'
const position2: Position = 'center'
let position3: Position

Optional Properties & Parameters

type Config = {
  mode: 'production' | 'development' | 'testing'
  url: string
  port?: number
  debug: boolean | undefined // ใช้แบบ ?: แทนได้
  experimental?: {
    featureA?: boolean
    featureB?: boolean
    featureC?: boolean
  }
}

function addUser(name: string, birthDate?: Date, location?: string) {
  // ...
}

Generics

function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
  return arr.map(fn)
}
const numbers1: Array<number> = [1, 2, 3, 4, 5]
const numbers2: number[] = [1, 2, 3, 4, 5]

type PostData = {
  userId: number
  id: number
  title: string
  body: string
}

async function fetchData<T>(url: string): Promise<T> {
  return await fetch(url).then((res) => res.json())
}

const data1 = await fetchData<PostData>('https://jsonplaceholder.typicode.com/posts/1')
const data2 = await fetchData<PostData[]>('https://jsonplaceholder.typicode.com/posts')
const data3 = await fetchData<{ id: number; title: string;}>('https://dummyjson.com/products/1?select=title')

Generic Constraints

function getLongest<T extends { length: number }>(a: T, b: T): T {
  return a.length > b.length ? a : b
}

class SaveSession<T extends { role: string; email: string }> {
  static id = 0
  data: T
  constructor (data: T) {
    SaveSession.id++
    this.data = data
  }
  parseSession(): string {
    return JSON.stringify(this.data)
  }
}

Type Assertion

const el1 = document.querySelector('input') as HTMLInputElement | null
const el2 = <HTMLInputElement | null>document.querySelector('input')
if (el1) {
  el1.value = 'Hello, world!'
  el1.focus()
}

type ProductData = {
  id: number
  title: string
}
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1')
const data = await response.json() as ProductData

Modules (ESM)

// ./users.ts
export const users: User[] = []

export type User = {
  id: number
  name: string
  email: string
}

export const addUser = (user: Omit<User, 'id'>) => {
  users.push({ ...user, id: users.length + 1 })
}

export function getUser(id: number): User | undefined {
  return users.find((user) => user.id === id)
}
// ./index.ts
import { users, addUser, getUser } from './users'

addUser({ name: 'John', email: '[email protected]' })
addUser({ name: 'Jane', email: '[email protected]' })
const user = getUser(1)

Immutable

// Object (interface)
interface Config {
  mode: 'production' | 'development' | 'testing'
  readonly databasePassword: string
  experimental?: {
    featureA?: boolean
    featureB?: boolean
  }
}
const config: Config = {
  mode: 'production',
  databasePassword: 'originPassword',
}
config.databasePassword = 'newPassword' // ❌ Cannot assign to '...' because it is a read-only property.

// OOP (class)
class User {
  readonly id: number
  email: string
  constructor(id: number, email: string) {
    this.id = id
    this.email = email
  }
}
const user = new User(1, '[email protected]')
user.email = '[email protected]'
user.id = 99 // ❌ Cannot assign to '...' because it is a read-only property.

Index Signature

interface Post {
  id: number
  title: string
  [key: string]: string | number
}
const post: Post = {
  id: 1,
  title: 'Hello, world!',
  author: 'John Doe',
  views: 100
}

Enum

enum Direction {
  Top,    // = 0
  Bottom, // = 1
  Left,   // = 2
  Right,  // = 3
}
const position: Direction = Direction.Bottom
console.log(position) // 1

enum HttpStatus {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  Unauthorized = 401,
  Forbidden = 403,
  NotFound = 404,
  InternalServerError = 500,
}
const status: HttpStatus = HttpStatus.NotFound
console.log(status) // 404

this

class Counter {
  count = 0
  increment(): this {
    this.count++
    return this
  }
  decrement(): this {
    this.count--
    return this
  }
}

const counter = new Counter()
counter.increment().increment()
console.log(counter.count) // 2

keyof

interface Product {
  name: string
  price: number
  quantity: number
}
type ProductKeys = keyof Product
const keys: ProductKeys[] = ['name', 'price', 'quantity']

interface SmartphonePrice {
  phoneA: 26900
  phoneB: 0
  phoneC: 8900
}
type SmartphoneAvailable = { // ไม่สามารถใช้แบบ interface ได้
  [key in keyof SmartphonePrice]: boolean
}
const smartphoneAvailable: SmartphoneAvailable = {
  phoneA: true,
  phoneB: false,
  phoneC: true
}

typeof

const hotel = {
  name: 'My Hotel',
  rooms: 100,
  location: 'Phuket',
}
type Hotel = typeof hotel
interface Motel extends Hotel {
  size: 'small' | 'medium' | 'large'
}
const motel: Motel = {
  name: 'My Motel',
  rooms: 100,
  location: 'Phuket',
  size: 'medium',
}

Indexed Access Types

const foods = [
  { name: 'Pizza', price: 190 },
  { name: 'Burger', price: 65 },
  { name: 'Pasta', price: 80 },
]
type Foods = typeof foods        // { name: string; price: number }[]
type Food = typeof foods[number] // { name: string; price: number }
const firedChicken: Food = { name: 'Fired Chicken', price: 95 }

Conditional Types

// T extends U ? X : Y
type IsString<T> = T extends string ? true : false

type S = IsString<string> // true
type N = IsString<number> // false

const string: S = true
const number: N = false

// Example with APIResponse
type APIList = 'user' | 'post' | 'comment'

type APIResponse<T> = T extends 'user'
  ? { id: number; name: string }
  : T extends 'post'
  ? { id: number; title: string }
  : T extends 'comment'
  ? { id: number; message: string }
  : never

function fetchData<T extends APIList>(type: T): APIResponse<T> {
  if (type === 'user') {
    return { id: 1, name: 'John Doe' } as APIResponse<T>
  } else if (type === 'post') {
    return { id: 1, title: 'My First Post' } as APIResponse<T>
  } else if (type === 'comment') {
    return { id: 1, message: 'Hello, world!' } as APIResponse<T>
  } else {
    throw new Error('Invalid type')
  }
}

const user = fetchData('user')
const post = fetchData('post')
const comment = fetchData('comment')
const product = fetchData('product') // ❌

Type Guards

function setHeight(height: string | number): string {
  if (typeof height === 'number') {
    return `${height}px`
  }
  return !height.endsWith('px') ? `${height}px` : height
}

const height1 = setHeight(100)   // '100px'
const height2 = setHeight('200') // '200px'
interface Product {
  id: number
  title: string
  cost: number
  quantity: number
}

const products: Product[] = [
  { id: 1, title: 'Product 1', cost: 100, quantity: 10 },
  { id: 2, title: 'Product 2', cost: 200, quantity: 5 },
]

function showProduct(id: number): string {
  const product = products.find(product => product.id === id)
  if (!product) {
    throw new Error('Product not found')
  }
  return `Product: ${product.title}, Cost: ${product.cost}, Quantity: ${product.quantity}`
}

Equality Narrowing

type APIStatus =
  | { status: 'success'; data: string }
  | { status: 'error'; error: string }
  | { status: 'loading' }

function handleResponse(response: APIStatus) {
  if (response.status === 'success') {
    console.log(response.data)
  } else if (response.status === 'error') {
    console.error(response.error)
  } else if (response.status === 'loading') {
    console.log('Loading...')
  }
}

handleResponse({ status: 'success', data: 'Here the data' })
handleResponse({ status: 'error', error: 'There is error' })
handleResponse({ status: 'loading' })

in Operator Narrowing

type APIStatus =
  | { status: 'success'; data: string }
  | { status: 'error'; error: string }
  | { status: 'loading' }

function handleResponse(response: APIStatus) {
  if ('data' in response) {
    console.log(response.data)
  } else if ('error' in response) {
    console.error(response.error)
  } else {
    console.log('Loading...')
  }
}

handleResponse({ status: 'success', data: 'Here the data' })
handleResponse({ status: 'error', error: 'There is error' })
handleResponse({ status: 'loading' })

instanceof Narrowing

class Circle {
  radius: number
  constructor(radius: number) {
    this.radius = radius
  }
  getCircleArea() {
    return Math.PI * this.radius * this.radius
  }
}

class Rectangle {
  width: number
  height: number
  constructor(width: number, height: number) {
    this.width = width
    this.height = height
  }
  getRectangleArea() {
    return this.width * this.height
  }
}

function renderShape(shape: Circle | Rectangle) {
  if (shape instanceof Circle) {
    return shape.getCircleArea()
  } else if (shape instanceof Rectangle) {
    return shape.getRectangleArea()
  }
}

console.log(renderShape(new Circle(10)))
console.log(renderShape(new Rectangle(10, 20)))

Predicates

function isNumber(value: any): value is number {
  return typeof value === 'number'
}

function isString(value: any): value is string {
  return typeof value === 'string'
}

function trimAll(input: any) {
  if (isNumber(input)) {
    return String(input)
  } else if (isString(input)) {
    return input.trim()
  } else {
    return String(input).trim()
  }
}

console.log(trimAll(10))
console.log(trimAll('  10  '))

Function Overloads

function search(items: string[], query: string): string[]
function search(items: number[], query: string): number[]
function search(items: (string | number)[], query: string): (string | number)[] | undefined {
  if (isStringArray(items)) {
    return items.filter(item => item.includes(query))
  } else if (isNumberArray(items)) {
    return items.filter(item => item.toString().includes(query))
  }
}

Classes

class Employee implements Person {
  readonly id: string
  public name: string
  private salary: number

  constructor(public name: string, private salary: number, protected role: EmployeeRole) {}

  private getSalaryAsYearly() { // เรียกใช้งานได้เฉพาะภายใน Class
    return this.salary * 12
  }

  getInfo() { // การที่ไม่เขียนอะไรนำหน้า = public
    return `${this.id} ${this.name} ${this.getSalaryAsYearly()} ${this.role}`
  }
}

Class Abstracts

abstract class Shape {
  abstract name: string
  abstract size: Record<string, number>
  abstract getArea(): number
  describe(): string {
    return `${this.name} with area ${this.getArea()} from ${JSON.stringify(this.size)}`
  }
}

class Rectangle extends Shape {
  name = 'Rectangle'
  size: Record<string, number>
  constructor(width: number, height: number) {
    super() // จำเป็นต้องเรียก หากมีการ extends abstract
    this.size = { width, height }
  }
  getArea(): number {
    return this.size.width * this.size.height
  }
}

const shape = new Shape() // ❌ Cannot create an instance of an abstract class.