TS study: νƒ€μž… μ‹œμŠ€ν…œ (3)

@choi2021 Β· January 16, 2023 Β· 17 min read

🎚 νƒ€μž… μ‹œμŠ€ν…œ(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둜 섀정해놓은 속성이 λ³€ν™”ν–ˆμ„ λ•Œλ§Œ μ—…λ°μ΄νŠΈλ  수 있게 μ •μ˜ν–ˆλ‹€. νƒ€μž…κ³Ό 값이 동기화 λ˜μ–΄ 있기 λ•Œλ¬Έμ— μƒˆλ‘œμš΄ 속성이 μΆ”κ°€λ˜μ–΄λ„ λ°”λ‘œ μ—λŸ¬κ°€ λ°œμƒν•΄ μ•Œλ €μ€„ 수 μžˆλ‹€.

@choi2021
맀일의 μ‹œν–‰μ°©μ˜€λ₯Ό κΈ°λ‘ν•˜λŠ” κ°œλ°œμΌμ§€μž…λ‹ˆλ‹€.