π νμ μμ€ν (3)
νμ μμ€ν μ λν μ 리 μ€ λ§μ§λ§μΌλ‘ μ λ€λ¦, μΈλ±μ€ μκ·Έλμ², Array νμ κ³Ό readonlyμ λν΄ μ 리ν΄λ³΄λ € νλ€.
πΉ μ λ€λ¦
νμ μ€ν¬λ¦½νΈλ₯Ό μ¬μ©ν μλ‘ λλΌλ μ μ νμ μ λ€λ£¬λ€λ κ²μ μΆκ°μ μΈ λ³μμ ν¨μλ₯Ό λ€λ£¨λ κ² κ°μλ€. κ·Έ μ΄μ λ λ³μλ₯Ό μ¬ μ¬μ©νκ³ λ°λ³΅λλ λ‘μ§μ ν¨μλ‘ λΆλ¦¬νλ― νμ μ μ¬μ¬μ©μ±μ κ³ λ €ν΄μΌ νκΈ° λλ¬Έμ΄λ€. μ½λ λ°λ³΅μ μ€μ΄λ €λ λ Έλ ₯μ DRY(Don't Repeat Yourself)λ‘ λΆλ¦¬λ νλ‘κ·Έλλ°μ κΈ°λ³Έ μμΉμΌλ‘ νμ μλ μ μ©λλ€.
1. νμ λ€μ΄λ°
μ€λ³΅λλ λ³μκ° μλ€λ©΄ νλμ μμλ‘ μ ν΄μ μ¬μ©νλ―μ΄ νμ μ μ΄λ¦μ λΆμ¬μ μ¬μ©ν΄ μ€λ³΅μ μ κ±°ν μ μλ€.
// λ€μ΄λ° μ
function distance(a: { x: number; y: number }, b: { x: number; y: number }) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2))
}
// λ€μ΄λ° ν
interface Point2D {
x: number
y: number
}
function distance(a: Point2D, b: Point2D) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2))
}μ μμ λ x, yμ νμ
μ μ 리ν μ€λΈμ νΈ νμ
μ΄ λ°λ³΅λλ μν©μ΄λ€. μ΄μ μ ν΄κ²°νκΈ° μν΄ Point2Dλ‘ interfaceλ₯Ό μ μν΄ μλ‘ λ€λ₯΄κ² μ ν΄μ Έμλ νμ
μ νλλ‘ μ 리ν΄μ€ μ μλ€. μ΄λ¬ν λ°©λ²μ μμ μ 리ν ν¨μ ννμμΌλ‘ λ§€κ°λ³μμ λ°ν κ°μ νμ
μ μ 리νλ κ²κ³Ό κ°λ€.
// λ€μ΄λ° μ
function get(url: string, opts: Options): Promise<Response> {}
function post(url: string, opts: Options): Promise<Response> {}
// λ€μ΄λ° ν
type HTTPFunction = (url: String, opts: Options) => Promise<Response>
const get: HTTPFunction = (url, opts) => {}
const post: HTTPFunction = (url, opts) => {}2. extendsμ intersection μ°μ°μ (&)
νλμ νμ
μ΄ μκ³ ν΄λΉ νμ
μ μμ±μ λ€λ₯Έ μμ±μ μΆκ°ν νμ
μ λ§λ€ λ μλ‘κ² λ§λ€κΈ° λ³΄λ€ μμ λ°°μ λ extendsμ &λ₯Ό μ΄μ©ν΄ μμ±μ μΆκ°ν μ μλ€.
// λ³κ²½ μ
interface Person {
firstName: string
lastName: string
}
interface PersonWithBirthDate {
firstName: String
lastName: string
birth: Date
}
// λ³κ²½ ν
interface Person {
firstName: string
lastName: string
}
interface PersonWithBirthDate extends Person {
birth: Date
}
type PersonWithBirthDate = Person & { birth: Date }3. λΆλΆ νμ
κΈ°μ‘΄μ μ μν νμ
μ μΌλΆλΆ μμ±μ μ μν λ μλ‘κ² μ μνκ³ νμ₯ν΄μ μ¬μ©νλ€. νμ§λ§ λ
Όλ¦¬μ μΌλ‘ λ§μ§ μλ€κ³ λλ μ μ΄ λ§μλλ° μ±
μμλ μΈλ±μ±μ μ΄μ©ν΄ μ€λ³΅μ μ κ±°νλ λ°©λ²μ μ μνλ€.
interface State {
userId: string
pageTitle: string
recentFiles: string[]
pageContents: string
}
type TopNavState = {
userId: State["userId"]
pageTitle: State["pageTitle"]
recentFiles: State["recentFiles"]
}μ λ°©λ²μ μ΄μ©νλ©΄ λΆλΆ νμ
μ μ μν μ μλ€. νμ§λ§ μΌμΌμ΄ μμ±μ λμ΄ν΄ λ°λ³΅λλ λΆλΆμ΄ μλ€. μ΄μ μ ν΄κ²°νκΈ° μν΄ mappingμ μ΄μ©ν μ μλ€,
type TopNavState = {
[k in "userId" | "pageTitle" | "recentFiles"]: State[k]
}
type TopNavState = Pick<State, "userId" | "pageTitle" | "recentFiles">
// type Pick<T, K extends keyof T> = {
// [P in K]: T[P];
// };μ μμμ 첫 λ²μ§Έλ mappingμ μ΄μ©ν΄ λ°λ³΅λλ μμ± keyλ₯Ό μννλ©° Stateμ λμ
ν κ°μ νμ
μ λ°μμ μ½λ μ€λ³΅μ μ κ±°νλ€. μ΄λ¬ν mappingμ μ νΈνμ
PickμΌλ‘ μ μλμ΄ μμ΄ λ λ²μ§Έ μμ λ‘ μ’ λ νΈνκ² λνλΌ μ μλ€. Pickμ μ¬μ©ν λλ λ¨Όμ μ°Έμ‘°ν Typeμ κ°μ Έμ€κ³ ν΄λΉ νμ
μμ κ°μ Έμ¬ keyκ°μ λ λ²μ§Έ μΈμλ‘ μ λ¬ν Genericμ μ΄μ©ν΄ λνλΌ μ μλ€.
Pick util typeμ μ μ©νμ§λ§ μ‘°μ¬ν΄μΌ ν λΆλΆμ κ°μ²΄ typeμ μ μνλ€λ μ μ΄λ€.
interface SaveAction {
type: "save"
}
interface LoadAction {
type: "load"
}
type Action = SaveAction | LoadAction
type ActionType = "save" | "load"μ μμ μμ ActionTypeμ μ΄λ―Έ μ μν 'save'μ 'load'λ₯Ό λ€μ μ μ΄ μ½λ μ€λ³΅μ΄ λ°μνλ€. μ΄μ μ ν΄κ²°νκΈ° μν΄μ Pickμ μ΄μ©νλ©΄ λ κ² κ°μ§λ§ μ΄λλ mappingμ μ΄μ©νλ κ²μ΄ μλμ λ λ§λ€.
type ActionType = Action["type"] //'save' | 'load';
type ActionRec = Pick<Action, "type"> // {type:'save' | 'load'}4. keyof μ Partial util νμ
interface Options {
width: number
height: number
color: string
label: string
}
interface OptionUpdate {
width?: number
height?: number
color?: string
label?: string
}
class UIWidget {
constructor(init: Options) {}
update(options: OptionUpdate) {}
}μ μμ λ Optionsμ λμΌν keyλ₯Ό κ°μ§ OptionUpdateνμ
μ μ μνλλ° μ νμ μΌλ‘ λ§λ€κΈ° μν΄ μλ‘κ² μ μν κ²μ λ³Ό μ μλ€.
type OptionsUpdate = { [k in keyof Options]?: Options[k] }
class UIWidget {
constructor(init: Options) {}
update(options: Partial<Options>) {}
}
// type Partial<T> = {
// [P in keyof T]?: T[P];
// };μ μμ λ₯Ό 보면 keyofλ₯Ό μ΄μ©ν΄ Optionsμ keyλ€μ λ°μμ€κ³ μλ‘κ² μ μν΄ μ½λ μ€λ³΅μ μ κ±°νλ€. keyλ€μ λͺ¨λ μ νμ μΌλ‘ λ§λ€κΈ° μν΄ utilType Partialμ΄ μ‘΄μ¬νλ€. Partialμ μ μλ₯Ό 보면 μμ keyofλ₯Ό κ·Έλλ‘ μ¬μ©ν κ²μ λ³Ό μ μλ€.
5. typeof
typeofλ μμκ°μ νμ
μ μ μνκ³ μ¬ μ¬μ©ν λ μμ¦ κ°μ₯ λ§μ΄ μ¬μ©νλ μ°μ°μμΈ κ² κ°λ€. typeofλ μμ κ° λΏλ§ μλλΌ κ°μ λν΄ μκ³ μμ λ ν΄λΉ κ°μ νμ
μ μ μν λ κ°λ¨νκ² μ¬μ©ν μ μλ€.
// typeof μ
const INIT_OPTIONS = {
width: 640,
height: 40,
color: "s",
label: "s",
}
interface OPtions {
width: number
height: number
color: string
label: string
}
// typeof ν
type OPtions = typeof INIT_OPTIONS // { width: number; height: number; color: string;label: string; }ν¨μμ κ²½μ° λ°ν κ°μ νμ
μ μ μνκ³ μΆμ λ typeofμ utiltype ReturnTypeμ μ΄μ©ν μ μλ€.
function getUserInfo(userId: string) {
return {
userId,
name,
age,
height,
weight,
favoriteColor,
}
}
type UserInfo = ReturnType<typeof getUserInfo>
// type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;ReturnTypeμ μ μμ μ‘°κ±΄λΆ λΆλΆμ΄ μμ§ μ΄ν΄κ° λμ§ μμ μ΄νμ νλ² λ λ³Ό νμκ° μμ κ² κ°λ€.
μ λ€λ¦μ νμ
μ μ μ°νκ² μ¬μ©ν μ μλ μ₯μ μ κ°μ§μ§λ§ μ μ°μ±μ λ²μκ° μ€μνλ€. μ΄λ¬ν λ²μλ₯Ό μ μν΄μ£Όμ§ μλλ€λ©΄ νμ
μ μ μν΄μ£ΌκΈ° μ κ³Ό κ°μμ§ μ μμΌλ―λ‘ extendsμ keyofμ κ°μ μ°μ°μλ₯Ό μ΄μ©ν΄ λ²μλ₯Ό λͺ
μμ μΌλ‘ μ ν΄ μ€ μ μλ€.
type Pick<T, K> = {
[k in K]: T[k] // This type parameter might need an `extends string | number | symbol` constraint.
}
type Pick<T, K extends keyof T> = {
[k in K]: T[K]
}μμ μμ λ kμ λ²μλ₯Ό λͺ°λΌ μλ¬κ° λ°μνμ§λ§ extendsμ keyofλ₯Ό μ΄μ©ν΄ keyκ°μ νμ
μΌλ‘ μ μν΄ ν΄κ²°ν μ μλ€.
π μΈλ±μ€ μκ·Έλμ²
μΈλ±μ€ μκ·Έλμ²λ μ¬μ©νλ©΄μ μ΄λ €μ΄ λΆλΆ μ€ νλμλ€. ꡬ체μ μΈ νμ
μ μ§μ νμ§ λͺ»νκΈ° λλ¬Έμ Object.keysμ κ°μ΄ λ°°μ΄λ‘ λμ΄νλ λ°©μμ μ΄λ €μμ κ²ͺμλ€. μ΄λ² κΈ°νλ₯Ό ν΅ν΄ μ’ λ μ λλ‘ μ΄ν΄νκ³ μ¬μ©νκ³ μ νλ€.
type Rocket = { [property: string]: string }μ μμ μμ keyλ₯Ό μ νν λͺ μνμ§ μκΈ° λλ¬Έμ μλ μμ±μ λμμ λ°μ μ μκ³ , valueμ νμ μ΄ stringμ΄ μλλΌ λ€λ₯Έ νμ μ κ°μ§ μ μλ λ¨μ μ κ°μ§λ€. κ·Έλ κΈ° λλ¬Έμ λ³΄λ€ μ ννκ² typeμ μ μν νμκ° μλ€.
κ·Έλ¬λ©΄ μΈλ±μ€ μκ·Έλμ²λ μ΄λμ μ°μ¬μΌ ν κΉ? μΈλ±μ€ μκ·Έλμ²λ λμ λ°μ΄ν°λ₯Ό ννν λ μ¬μ©λμ΄μΌ νλ€. 미리 μ μ μλ λ°μ΄ν°λ₯Ό λ°μ μμΌ ν λ μ¬μ©ν μ μλ€.
function parseCSV(input: string): { [columnName: string]: string }[] {
const lines = input.split("\n")
const [header, ...rows] = lines
const headerColumns = header.split(",")
return rows.map(rowStr => {
const row: { [columnName: string]: string } = {}
rowStr.split(",").forEach((cell, i) => {
row[headerColumns[i]] = cell
})
return row
})
}μ€μ λ‘ μ¬μ©ν κ°μ λν΄μ μ μ μμ λ μ¬μ©νλ κ²μ΄ μΈλ±μ€ μκ·Έλμ²μ λ³Έμ§μ΄λ―λ‘ λ΄κ° μ¬μ©νλ λ°©μμ μλͺ»λ λ°©μμ΄μμμ κΉ¨λ¬μ μ μμλ€.
μΈλ±μ€ μκ·Έλμ²λ stringνμ
μΌλ‘ κ΄λ²μνλ―λ‘ λ체ν μ μλ λ°©λ²λ€μ΄ μ‘΄μ¬νλ€.
type Vec3D = Record<"x" | "y" | "z", number>
// type Record<K extends keyof any, T> = {
// [P in K]: T;
// };Record utilTypeμ keyμ νμ
μ μ μ°νκ² ν΄μ£Όλ©΄μλ λ²μλ₯Ό μ ν΄ μ€ μ μλ€.
π Array, Tuple, ArrayLike
λ°°μ΄μ μ€λΈμ νΈλ€. κ·Έλ κΈ° λλ¬Έμ μ€λΈμ νΈλ₯Ό μ¬μ©νλ― λ¬Έμμ΄ keyλ‘ λ°°μ΄ μμμ μ κ·Όν μ μλ€.
const xs = [1, 2, 3]
const x0 = xs[0]
const x1 = xs["1"]
console.log(x1) // 2νμ
μ€ν¬λ¦½νΈμ Arrayλ μ«μ ν€λ§μ νμ©νκ³ λ¬Έμμ΄ ν€λ λ€λ₯΄κ² μΈμν΄ key typeμ νλλ‘ μ ν΄ μ€λ€.
function get<T>(array: T[], k: string): T {
return array[k] // Element implicitly has an 'any' type because index expression is not of type 'number'.
}μ€μ λ°νμμ λ¬Έμμ΄ ν€λ‘ μΈμνμ§λ§ νμ 체ν¬λ₯Ό ν΅ν΄ μ€λ₯λ₯Ό μ‘μ μ μλ μ₯μ μ κ°μ§ μ μλ€. μ± μ μ½μΌλ©΄μ μ΄λ»κ² μ μ©νλ©΄ μ’μμ§ κ³ λ―Όμ ν΄λ΄€μ§λ§, μΌλ¨μ μ΄λ¬ν λΆλΆμ΄ μꡬλ μ΄ν΄νκ³ λμ΄κ°μ λμ€μ λ€μ λ³΄κΈ°λ‘ μκ°νλ€.
π Readonly
Readonlyλ λ§ κ·Έλλ‘ μ½κΈ°λ§ κ°λ₯νκ² ν΄μ€ μ μλ μ°μ°μλ‘ ν΄λΉ λ³μλ₯Ό λ³νμν€λ κ²μ μλ¬λ₯Ό λμ§κ² ν΄ μλ³Έμ 보쑴νλ λ° λμμ μ€λ€. μ΄λ¬ν μ μ΄ μ€μν κ²μ call by referenceλ₯Ό κ³ λ €ν΄ κ°μ²΄ νμ
μ λ§€κ°λ³μλ₯Ό λ€λ€μΌ ν¨μλ‘ μΈν side-effectλ₯Ό λ§μ μ μκΈ° λλ¬Έμ΄λ€. κ°μ²΄μ κ²½μ°μ μλ³Έμ κ°μ΄ λ€μ΄μ€λ κ²μ΄ μλλΌ referenceκ°μ΄ μ λ¬λκΈ° λλ¬Έμ ν¨μ λ΄λΆμμ λ³νμν¨λ€λ©΄ μλ³Έμ λ³νκ° μκΈ°λ λ¬Έμ μ μ΄ μκΈ΄λ€.
μ΄λ¬ν λ¬Έμ μ μ λ§μ μ μλ λ°©λ²μ΄ λ°λ‘ readonlyμ°μ°μλ€.
function arraySum(arr: readonly number[]) {
let sum = 0,
num
while ((num = arr.pop()) !== undefined) {
// Property 'pop' does not exist on type 'readonly number[]'.
sum += num
}
return sum
}μμ μμ λ popκ³Ό κ°μ΄ μλ³Έ μμλ₯Ό λ°κΎΈλ λ©μλλ₯Ό νΈμΆν μ μκΈ° λλ¬Έμ μκΈ΄ μλ¬λ‘ ν¨μ λ΄μμ λ§€κ°λ³μ λ΄λΆμ λ³νκ° μκΈ°μ§ μκ² λ§λλ€. readonlyλ°°μ΄μ λ³κ²½ κ°λ₯ν λ°°μ΄μ ν λΉν μ μμ§λ§, λ°λλ λμ§ μλ νΉμ§μ κ°μ§λ€.
// λ³κ²½ μ
function parseTaggedText(lines: string[]): string[][] {
const paragraphs: string[][] = []
const currPara: readonly string[] = []
const addParagraph = () => {
if (currPara.length) {
paragraphs.push(currPara) //readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'
currPara.length = 0
}
}
for (const line of lines) {
if (!line) {
addParagraph()
} else {
currPara.push(line)
}
}
addParagraph()
return paragraphs
}μμ λ§νλ λλ‘ readonlyλ°°μ΄μ λ³κ²½ κ°λ₯ν λ°°μ΄μ ν λΉν μ μμ΄ μλ¬κ° λ°μν κ²μ λ³Ό μ μλ€. μ΄λ κ² μλ³Έμ νΌμνμ§ μκ³ μ¬μ©νκΈ° μν΄μλ μμ 리μ‘νΈλ₯Ό 곡λΆνλ©° μ 리ν λΆλ³μ±μ μ§μΌμ£Όλ©΄ λλ€.
function parseTaggedText(lines: string[]): string[][] {
const paragraphs: string[][] = []
let currPara: readonly string[] = []
const addParagraph = () => {
if (currPara.length) {
paragraphs.push([...currPara]) // μμ 볡μ¬λ‘ μλ‘μ΄ λ°°μ΄μ λ§λ€μ΄
currPara = []
}
}
for (const line of lines) {
if (!line) {
addParagraph()
} else {
currPara = currPara.concat([line])
}
}
addParagraph()
return paragraphs
}Readonly util νμ
Readonly util νμ
μ κ°μ²΄μ μμ±μ readonlyλ‘ λ§λ€μ΄μ€λ€.
const o: Readonly<Outer> = { inner: { x: 0 } }
o.inner = { x: 1 } // Cannot assign to 'inner' because it is a read-only property.
// type Readonly<T> = {
// readonly [P in keyof T]: T[P];
// };π Mappingλ νμ μ μ΄μ©ν΄ κ° λκΈ°ννκΈ°
interface ScatterProps {
xs: number[]
ys: number[]
xRange: [number, number]
yRange: [number, number]
color: string
onClick: (x: number, y: number, index: number) => void
}
function shouldUpdate(oldProps: ScatterProps, newProps: ScatterProps) {
let k: keyof ScatterProps
for (k in oldProps) {
if (oldProps[k] !== newProps[k]) {
if (k !== "onClick") return true
}
}
return false
}μ μμ μμ ScatterProps interfaceλ₯Ό μ΄μ©ν΄μ νμ¬ Propsμ newPropsλ₯Ό λΉκ΅ν΄ propsκ° λ³κ²½λ κ²½μ° μ§λλ₯Ό λ€μ 그리λ μμ λ₯Ό λ΄κ³ μλ€. μ€μ 리μ‘νΈμμ μ»΄ν¬λνΈκ° re-renderingλλ 쑰건 μ€ νλκ° propsλ μνκ° λ°λλ κ²½μ°λ‘ κΈ°μ‘΄ propsμμ μ°¨μ΄λ₯Ό μμ λΉκ΅λ₯Ό ν΅ν΄ λΉκ΅νλ€.
νμ¬ μ μλ shouldUpdateμμ onClickν¨μ λ³νλ μ
λ°μ΄νΈμ μν₯μ μ£Όμ§ μκ² μ€μ λμ΄ μλ€. νμ§λ§ λ§μ½ μλ‘μ΄ μμ±μ΄ μΆκ°λλ κ²½μ°μ νμ trueμ΄κΈ° λλ¬Έμ λ무 μμ£Ό μλ‘ κ·Έλ €μ§κ² λλ€. μ΄μ μ λ§κΈ° μν΄μ λ€μκ³Ό κ°μ μ½λλ‘ μμ ν μ μλ€.
function shouldUpdate(oldProps: ScatterProps, newProps: ScatterProps) {
return (
oldProps.xs !== newProps.xs ||
oldProps.ys !== newProps.ys ||
oldProps.xRange !== newProps.xRange ||
oldProps.yRange !== newProps.yRange ||
oldProps.color !== newProps.color
)
}μ μ½λλ μΌμΌμ΄ μμ±λ€μ λ³νλ₯Ό 체ν¬νλ λ°©μμΌλ‘ λ§€λ² μλ‘μ΄ μμ±μ΄ μΆκ°λ λλ§λ€ μμ±ν΄ μ£Όμ΄μΌ νλ―λ‘ λΉν¨μ¨μ μ΄λ€.
μ’ λ μ΄μμ μΈ λ°©λ²μ μλ‘μ΄ μμ±μ΄ μΆκ°λ λ νμ 체컀μ λ§€νλ νμ ,κ°μ²΄λ₯Ό μ΄μ©νλ μ½λλ€. λ€μ μμ λ₯Ό 보μ.
const REQUIRES_UPDATE: { [k in keyof ScatterProps]: boolean } = {
xs: true,
ys: true,
xRange: true,
yRange: true,
color: true,
onClick: false,
}
function shouldUpdate(oldProps: ScatterProps, newProps: ScatterProps) {
let k: keyof ScatterProps
for (k in oldProps) {
if (oldProps[k] !== newProps[k] && REQUIRES_UPDATE[k]) {
return true
}
}
return false
}keyofλ₯Ό μ΄μ©ν΄ Updateκ° νμν μμ±λ€μ λν΄ λͺ μν΄λκ³ , trueλ‘ μ€μ ν΄λμ μμ±μ΄ λ³ννμ λλ§ μ λ°μ΄νΈλ μ μκ² μ μνλ€. νμ κ³Ό κ°μ΄ λκΈ°ν λμ΄ μκΈ° λλ¬Έμ μλ‘μ΄ μμ±μ΄ μΆκ°λμ΄λ λ°λ‘ μλ¬κ° λ°μν΄ μλ €μ€ μ μλ€.