🔗 ลิงก์ที่น่าสนใจ
- Docs:
- Awesome:
- Compiler & Execution
- Bundlers
- Alternative Runtimes
- Packages
- DefinitelyTyped
⚙️ tsconfig.json
Option | Description |
---|
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
Code | Description |
---|
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.