2024-05-01.md

🏑

DIL: μ΄νŽ™ν‹°λΈŒ νƒ€μž…μŠ€ν¬λ¦½νŠΈ

μŠ€ν„°λ””: μ›”κ°„ CS, https://github.com/monthly-cs/2024-05-effective-typescript
μž‘μ„±μΌ: 2024-05-01
μž‘μ„±μž: dusunax


μ•„μ΄ν…œ 1: νƒ€μž…μŠ€ν¬λ¦½νŠΈμ™€ μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ 관계 μ΄ν•΄ν•˜κΈ° - TS vs JS

  • "νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ superset(μƒμœ„μ§‘ν•©)이닀."
  • "νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” νƒ€μž…μ΄ μ •μ˜λœ μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ superset(μƒμœ„μ§‘ν•©)이닀."

Superset

  • νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” λ¬Έλ²•μ μœΌλ‘œλ„ μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ μƒμœ„μ§‘ν•©μ΄λ‹€.
    • JS에 문법 였λ₯˜ x => μœ νš¨ν•œ νƒ€μž…μŠ€ν¬λ¦½νŠΈ ν”„λ‘œκ·Έλž¨
    • JS에 이슈 => 문법 였λ₯˜κ°€ μ•„λ‹ˆλ”λΌλ„, νƒ€μž… μ²΄μ»€μ—κ²Œ 지적
    • λ¬Έλ²•μ˜ μœ νš¨μ„±κ³Ό λ™μž‘ μ΄μŠˆλŠ” 독립적 문제

뢀뢄집합

  • λͺ¨λ“  μžλ°”μŠ€ν¬λ¦½νŠΈ ν”„λ‘œκ·Έλž¨μ€ νƒ€μž…μŠ€ν¬λ¦½νŠΈλ‹€ => O
    • JS -> TS λ§ˆμ΄κ·Έλ ˆμ΄μ…˜μ— 이점
  • λͺ¨λ“  νƒ€μž…μŠ€ν¬λ¦½νŠΈ ν”„λ‘œκ·Έλž¨μ€ μžλ°”μŠ€ν¬λ¦½νŠΈλ‹€ => X
    • νƒ€μž…μ„ λͺ…μ‹œν•˜λŠ” 좔가적인 문법을 가지기 λ•Œλ¬Έ image

μžλ°”μŠ€ν¬λ¦½νŠΈμ™€ νƒ€μž… 체컀

let city = "new york city";
console.log(city.toUppercase());
//               ~~~~~~~~~~~ Property 'toUppercase' does not exist on type
//                           'string'. Did you mean 'toUpperCase'?
  • νƒ€μž… μ²΄μ»€λŠ” νƒ€μž… 좔둠을 톡해, μžλ°”μŠ€ν¬λ¦½νŠΈ ν”„λ‘œκ·Έλž¨μ—μ„œλ„ μœ μš©ν•˜κ²Œ 였λ₯˜λ₯Ό μ°Ύμ•„λ‚Ό 수 μžˆλ‹€.
  • λŸ°νƒ€μž„μ— 였λ₯˜λ₯Ό λ°œμƒμ‹œν‚¬ μ½”λ“œ μ°ΎκΈ°: 정적 νƒ€μž… μ‹œμŠ€ν…œ
const states = [
  { name: "Alabama", capital: "Montgomery" },
  { name: "Alaska", capital: "Juneau" },
  { name: "Arizona", capital: "Phoenix" },
  // ...
];

for (const state of states) {
  console.log(state.capitol);
  //                ~~~~~~~ Property 'capitol' does not exist on type
  //                '{ name: string; capital: string; }'.
  //                 Did you mean 'capital'?
  // μ˜€νƒ€λ₯Ό 찾을 수 μžˆμ§€λ§Œ, μ–΄λŠ μͺ½μ΄ μ˜€νƒ€μΈμ§€ μ•Œ 수 μ—†λ‹€.
}
// λͺ…μ‹œμ μœΌλ‘œ States μ„ μ–Έ μ‹œ
interface State {
  name: string;
  capital: string;
}
const states: State[] = [
  // νƒ€μž… ꡬ문을 μΆ”κ°€ν•˜λ©΄ 였λ₯˜λ₯Ό 찾을 수 μžˆλ‹€
  { name: "Alabama", capital: "Montgomery" },
  { name: "Alaska", capital: "Juneau" },
  { name: "Arizona", capitol: "Phoenix" },
  //                 ~~~~~~~~~~~~~~~~~~ Object literal may only specify known
  //                          properties, but 'capitol' does not exist in type
  //                         'State'.  Did you mean to write 'capital'?
  // ...
];
for (const state of states) {
  console.log(state.capital);
}

νƒ€μž… 체컀λ₯Ό ν†΅κ³Όν•œ νƒ€μž…μŠ€ν¬λ¦½νŠΈ ν”„λ‘œκ·Έλž¨

  • ν‰μ†Œ μž‘μ„±ν•˜λŠ” νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ½”λ“œ = 였λ₯˜κ°€ μ—†λŠ” μƒνƒœμ˜, νƒ€μž… 체컀λ₯Ό ν†΅κ³Όν•œ ν”„λ‘œκ·Έλž¨

image

  • νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ νƒ€μž… μ‹œμŠ€ν…œμ€ μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ λŸ°νƒ€μž„ λ™μž‘μ„ 'λͺ¨λΈλ§' ν•œλ‹€.
  • λ˜ν•œ, μ˜λ„μΉ˜ μ•Šμ€ μ½”λ“œκ°€ 였λ₯˜λ‘œ μ΄μ–΄μ§ˆ 수 μžˆλŠ” 경우λ₯Ό 고렀함
const x = 2 + "3"; // OK, type is string
const y = "2" + 3; // OK, type is string
  • μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œλŠ” 였λ₯˜κ°€ λ°œμƒν•˜μ§€ μ•ŠμœΌλ‚˜, νƒ€μž… 체컀λ₯Ό λ¬Έμ œμ μ„ ν‘œμ‹œν•œλ‹€.
const a = null + 7; // Evaluates to 7 in JS
//        ~~~~ Operator '+' cannot be applied to types ...
const b = [] + 12; // Evaluates to '12' in JS
//        ~~~~~~~ Operator '+' cannot be applied to types ...
alert("Hello", "TypeScript"); // alerts "Hello"
//            ~~~~~~~~~~~~ Expected 0-1 arguments, but got 2
  • νƒ€μž…μŠ€ν¬λ¦½νŠΈκ°€ μ΄ν•΄ν•˜λŠ” κ°’μ˜ νƒ€μž…κ³Ό μ‹€μ œ κ°’μ˜ 차이가 있기 λ•Œλ¬Έμ— 였λ₯˜κ°€ λ°œμƒν•˜μ§€ μ•ŠλŠ” μ½”λ“œ
const names = ["Alice", "Bob"];
console.log(names[2].toUpperCase());
  • νƒ€μž… μ‹œμŠ€ν…œμ€ 정적 νƒ€μž…μ˜ 정확성을 보μž₯ν•˜λŠ” 것이 λͺ©μ μ΄ μ•„λ‹˜
    • μ •ν™•μ„± 보μž₯ λͺ©μ μ΄λΌλ©΄? Reason, Elm 같은 μ–Έμ–΄λ₯Ό μ„ νƒν•œλ‹€. (λŸ°νƒ€μž„ μ•ˆμ •μ„±μ„ 보μž₯ν•˜λŠ” λŒ€μ‹ , JS의 Superset이 μ•„λ‹ˆλ―€λ‘œ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 과정이 λ³΅μž‘ν•˜λ‹€.)

βœ”οΈ μš”μ•½

  • νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ Superset이닀.
    • λͺ¨λ“  μžλ°”μŠ€ν¬λ¦½νŠΈ ν”„λ‘œκ·Έλž¨μ€ 이미 νƒ€μž…μŠ€ν¬λ¦½νŠΈ ν”„λ‘œκ·Έλž¨μ΄λ‹€. λ°˜λŒ€λ‘œ, νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” λ³„λ„μ˜ 문법을 가지고 있기 λ•Œλ¬Έμ— μΌλ°˜μ μœΌλ‘œλŠ” μœ νš¨ν•œ μžλ°”μŠ€ν¬λ¦½νŠΈ ν”„λ‘œκ·Έλž¨μ΄ μ•„λ‹ˆλ‹€.
  • νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” μžλ°”μŠ€ν¬λ¦½νŠΈ λŸ°νƒ€μž„ λ™μž‘μ„ λͺ¨λΈλ§ν•˜λŠ” νƒ€μž… μ‹œμŠ€ν…œμ„ 가지고 μžˆλ‹€.
    • λŸ°νƒ€μž„ 였λ₯˜λ₯Ό λ°œμƒμ‹œν‚€λŠ” μ½”λ“œλ₯Ό μ°Ύμ•„λ‚΄λ € ν•œλ‹€.
    • λ‹€λ§Œ, νƒ€μž… 체컀λ₯Ό ν†΅κ³Όν•˜λ©΄μ„œλ„ λŸ°νƒ€μž„ 였λ₯˜λ₯Ό λ°œμƒν‚€λŠ” μ½”λ“œλŠ” μΆ©λΆ„νžˆ μ‘΄μž¬ν•œλ‹€.
  • νƒ€μž…μŠ€ν¬λ¦½νŠΈ νƒ€μž… μ‹œμŠ€ν…œμ€ μ „λ°˜μ μœΌλ‘œ μžλ°”μŠ€ν¬λ¦½νŠΈ λ™μž‘μ„ λͺ¨λΈλ§ν•œλ‹€.
    • κ·ΈλŸ¬λ‚˜, 잘λͺ»λœ λ§€κ°œλ³€μˆ˜ κ°œμˆ˜μ™€ 같이 μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œλŠ” ν—ˆμš©λ˜μ§€λ§Œ νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œλŠ” λ¬Έμ œκ°€ λ˜λŠ” κ²½μš°κ°€ 있으며 엄격함은 μ˜¨μ „νžˆ μ·¨ν–₯의 차이닀.

μ•„μ΄ν…œ 2: νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ„€μ • μ΄ν•΄ν•˜κΈ° - Which TS

  • μ„€μ •
    • cli tsc --noImplicitAny program.ts
    • tsconfig.json {"compilerOptions": {"noImplicitAny": true}}
  • tsconfig.json을 μ‚¬μš©ν•˜λŠ” 게 쒋은 이유
    • νƒ€μž…μŠ€ν¬λ¦½νŠΈλ₯Ό μ–΄λ–»κ²Œ μ‚¬μš©ν•  κ³„νšμΈμ§€, λ™λ£Œλ‚˜ λ‹€λ₯Έ 도ꡬ가 μ•Œ 수 μžˆλ‹€
    • tsc --init
  • {"strict": true} 섀정을 톡해, λŒ€λΆ€λΆ„μ˜ 였λ₯˜λ₯Ό μž‘μ•„λ‚Ό 수 μžˆλ‹€.
    • νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ½”λ“œλ₯Ό κ³΅μœ ν–ˆμ„ λ•Œ νƒ€μž… 체컀 κ²°κ³Όκ°€ λ‹€λ₯΄λ‹€λ©΄, λ™μΌν•œ 컴파일러 섀정을 κ°–κ³  μžˆλŠ”μ§€ 확인해야 ν•œλ‹€.

implicitAny

  • μ•”μ‹œμ μΈ, μ•”λ¬΅μ μœΌλ‘œ ν•©μ˜λœ any
    • true둜 νƒ€μž… λͺ…μ‹œν•˜λ„λ‘ ν•΄μ•Ό ν•˜λŠ” 이유: νƒ€μž…μŠ€ν¬λ¦½νŠΈκ°€ 문제λ₯Ό λ°œκ²¬ν•˜κΈ° μˆ˜μ›”ν•˜κ³ , μ½”λ“œμ˜ 가독성이 쒋아지며, 개발자의 생산성이 ν–₯상
    • false둜 λ‘λŠ” 경우: λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ λ“± 일뢀 경우
// βœ… tsConfig: {"noImplicitAny":false}
function add(a, b) {
  return a + b;
}

// ❌ tsConfig: {"noImplicitAny":true}
function add(a, b) {
  //         ~    Parameter 'a' implicitly has an 'any' type
  //            ~ Parameter 'b' implicitly has an 'any' type
  return a + b;
}

// (νƒ€μž… λͺ…μ‹œ) tsConfig: {"noImplicitAny":true}
function add(a: number, b: number) {
  return a + b;
}

strictNullChecks

  • nullκ³Ό undefinedκ°€ λͺ¨λ“  νƒ€μž…μ—μ„œ ν—ˆμš©λ˜λŠ”κ°€
    • true둜 null 체크해야 ν•˜λŠ” 이유: 'undefinedλŠ” 객체가 μ•„λ‹™λ‹ˆλ‹€'와 같은 λŸ°νƒ€μž„ 였λ₯˜κ°€ λ°œμƒν•  수 μžˆλ‹€.
    • false둜 λ‘λŠ” 경우: λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ λ“± 일뢀 경우
  • strictNullChecksκ°€ true이렀면, noImplicitAnyκ°€ λ¨Όμ € trueμ—¬μ•Ό 함
// tsConfig: {"noImplicitAny":true,"strictNullChecks":false}
const x: number = null; // OK, null is a valid number\

// tsConfig: {"noImplicitAny":true,"strictNullChecks":true}
const x: number = null;
//    ~ Type 'null' is not assignable to type 'number'

const x: number | null = null; // null ν—ˆμš©μ„ λͺ…μ‹œμ μœΌλ‘œ λ“œλŸ¬λƒ„
  • null 체크 μ½”λ“œ or 단언
// tsConfig: {"noImplicitAny":true,"strictNullChecks":true}

const el = document.getElementById("status");
el.textContent = "Ready";
// ~~ Object is possibly 'null'

if (el) {
  // (1) null μ²΄ν¬ν•˜κΈ°
  el.textContent = "Ready"; // OK, null has been excluded
}
// assertion, 단언
el!.textContent = "Ready"; // OK, we've asserted that el is non-null

μš”μ•½

  • νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ»΄νŒŒμΌλŸ¬λŠ” μ–Έμ–΄μ˜ 핡심 μš”μ†Œμ— 영ν–₯을 λ―ΈμΉ˜λŠ” 섀정을 ν¬ν•¨ν•œλ‹€
  • cliλ³΄λ‹€λŠ” tsconfig.json을 μ‚¬μš©ν•˜λŠ” 것이 μ’‹λ‹€
    • μ—„κ²©ν•œ 체크λ₯Ό μœ„ν•΄ strict 섀정을 κ³ λ €ν•œλ‹€.
    • noImplicitAny와 strictNullChecksλ₯Ό μ„€μ •ν•˜λŠ” 것이 μ’‹λ‹€.

μ•„μ΄ν…œ 3: μ½”λ“œ 생성과 νƒ€μž…μ΄ κ΄€κ³„μ—†μŒμ„ μ΄ν•΄ν•˜κΈ° - Independent

νƒ€μž…μŠ€ν¬λ¦½νŠΈ 컴파일러

  1. transpile 트랜슀파일: μ΅œμ‹  νƒ€μž…μŠ€ν¬λ¦½νŠΈ, μžλ°”μŠ€ν¬λ¦½νŠΈλ₯Ό λΈŒλΌμš°μ €μ—μ„œ λ™μž‘ν•  수 μžˆλ„λ‘ κ΅¬λ²„μ „μ˜ μžλ°”μŠ€ν¬λ¦½νŠΈλ‘œ 트랜슀파일 ν•œλ‹€.
  2. μ½”λ“œμ˜ νƒ€μž… 였λ₯˜λ₯Ό μ²΄ν¬ν•œλ‹€.

Translateκ³Ό Compile이 합쳐져 νŠΈλžœμŠ€νŒŒμΌμ΄λΌλŠ” μ‹ μ‘°μ–΄κ°€ 탄생함
μ†ŒμŠ€μ½”λ“œλ₯Ό λ™μΌν•œ λ™μž‘μ„ ν•˜λŠ” λ‹€λ₯Έ ν˜•νƒœμ˜ μ†ŒμŠ€μ½”λ“œ (λ‹€λ₯Έ 버전, λ‹€λ₯Έ μ–Έμ–΄)둜 λ³€ν™”ν•˜λŠ” ν–‰μœ„. 결과물이 "μ»΄νŒŒμΌλ˜μ–΄μ•Όν•˜λŠ” μ†ŒμŠ€μ½”λ“œ"이기 λ•Œλ¬Έμ— 컴파일과 κ΅¬λΆ„ν•˜μ—¬ λΆ€λ₯Έλ‹€.

  • 1κ³Ό 2λŠ” 독립적
    • 1 => νƒ€μž…μŠ€ν¬λ¦½νŠΈκ°€ μžλ°”μŠ€ν¬λ¦½νŠΈλ‘œ λ³€ν™˜λ  λ•Œ, μ½”λ“œ λ‚΄μ˜ νƒ€μž…μ—λŠ” 영ν–₯을 주지 μ•ŠλŠ”λ‹€. μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ μ‹€ν–‰ μ‹œμ μ—λ„ νƒ€μž…μ€ 영ν–₯을 λ―ΈμΉ˜μ§€ μ•ŠμŒ
      • 즉, νƒ€μž… 였λ₯˜κ°€ μžˆλŠ” μ½”λ“œλ„ 컴파일이 κ°€λŠ₯ν•˜λ‹€.

컴파일

  • μ»΄νŒŒμΌμ€ νƒ€μž… 체크와 λ…λ¦½μ μœΌλ‘œ λ™μž‘ν•˜κΈ° λ•Œλ¬Έμ—, νƒ€μž… 였λ₯˜κ°€ μžˆλŠ” μ½”λ“œλ„ 컴파일이 κ°€λŠ₯
    • λ¬Έμ œκ°€ 될 뢀뢄을 μ•Œλ €μ£Όμ§€λ§Œ, λΉŒλ“œλ₯Ό λ©ˆμΆ”μ§€λŠ” μ•ŠλŠ”λ‹€.
      • 였λ₯˜κ°€ μžˆλ”λΌλ„ 컴파일된 μ‚°μΆœλ¬Όμ΄ μžˆλ‹€λ©΄, λ‹€λ₯Έ λΆ€λΆ„ ν…ŒμŠ€νŠΈ κ°€λŠ₯
    • 였λ₯˜κ°€ μžˆμ„ λ•Œ μ»΄νŒŒμΌν•˜μ§€ μ•ŠμœΌλ €λ©΄ tsconfig.json에 {"noEmitOnError":true} μ„€μ •

컴파일과 νƒ€μž… 체크
μ½”λ“œμ— 였λ₯˜κ°€ μžˆμ„ λ•Œ, "μ»΄νŒŒμΌμ— λ¬Έμ œκ°€ μžˆλ‹€"κ³  λ§ν•˜λŠ” 경우 기술적으둜 틀린말!

  • μ—„λ°€νžˆ μ½”λ“œ μƒμ„±λ§Œ 컴파일이라고 ν•  수 있음
  • νƒ€μž…μŠ€ν¬λ¦½νŠΈκ°€ μœ νš¨ν•œ μžλ°”μŠ€ν¬λ¦½νŠΈλΌλ©΄? μ»΄νŒŒμΌμ„ ν•œλ‹€.
    • κ·ΈλŸ¬λ―€λ‘œ, μ½”λ“œμ— 였λ₯˜κ°€ μžˆλ‹€? "νƒ€μž… 체크에 λ¬Έμ œκ°€ μžˆλ‹€"κ³  λ§ν•˜λŠ” 것이 μ •ν™•ν•˜λ‹€

λŸ°νƒ€μž„μ—λŠ” νƒ€μž… 체크 X

interface Square {
  width: number;
}
interface Rectangle extends Square {
  height: number;
}
type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  // ❗️ instanceof은 λŸ°νƒ€μž„μ— μΌμ–΄λ‚˜μ§€λ§Œ Ractangle은 νƒ€μž…μ΄λ‹€
  // μΈν„°νŽ˜μ΄μŠ€, νƒ€μž…, νƒ€μž… ꡬ문은 JS둜 μ»΄νŒŒμΌλ˜λŠ” κ³Όμ •μ—μ„œ μ œκ±°λœλ‹€. => erasable
  if (shape instanceof Rectangle) {
    //                 ~~~~~~~~~ 'Rectangle' only refers to a type,
    //                           but is being used as a value here
    return shape.width * shape.height;
    //                         ~~~~~~ Property 'height' does not exist
    //                         on type 'Shape'
  } else {
    return shape.width * shape.width;
  }
}

λŸ°νƒ€μž„μ— νƒ€μž… 정보λ₯Ό μœ μ§€ν•˜λŠ” 방법 A: 속성 체크

  • attribute 속성 μ²΄ν¬λŠ” λŸ°νƒ€μž„μ—λ§Œ μ ‘κ·Ό κ°€λŠ₯ν•œ 값에 κ΄€λ ¨
  • νƒ€μž… 체컀도 shape의 νƒ€μž…μ„ Rectangle둜 λ³΄μ •ν•˜κΈ° λ•Œλ¬Έμ— 였λ₯˜ 사라짐
interface Square {
  width: number;
}
interface Rectangle extends Square {
  height: number;
}
type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  // 속성 쑴재 μ—¬λΆ€λ₯Ό 체크~
  if ("height" in shape) {
    shape; // Type is Rectangle
    return shape.width * shape.height;
  } else {
    shape; // Type is Square
    return shape.width * shape.width;
  }
}

λŸ°νƒ€μž„μ— νƒ€μž… 정보λ₯Ό μœ μ§€ν•˜λŠ” 방법 B: Tagged Union

  • λŸ°νƒ€μž„μ— μ ‘κ·Ό κ°€λŠ₯ν•œ νƒ€μž…μ„ 정보λ₯Ό λͺ…μ‹œμ μœΌλ‘œ μ €μž₯ν•˜λŠ” νƒœκ·Έ 기법
interface Square {
  // unionμ΄λ‹ˆκΉŒ ~ tagged union
  kind: "square"; // λŸ°νƒ€μž„μ— μ ‘κ·Ό κ°€λŠ₯ν•œ tag
  width: number;
}
interface Rectangle {
  kind: "rectangle";
  height: number;
  width: number;
}
type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  if (shape.kind === "rectangle") {
    // tag 체크
    shape; // Type is Rectangle
    return shape.width * shape.height;
  } else {
    shape; // Type is Square
    return shape.width * shape.width;
  }
}

λŸ°νƒ€μž„μ— νƒ€μž… 정보λ₯Ό μœ μ§€ν•˜λŠ” 방법 C: νƒ€μž…μ„ 클래슀둜 λ§Œλ“€κΈ°

  • μΈν„°νŽ˜μ΄μŠ€λŠ” νƒ€μž…μœΌλ‘œλ§Œ μ‚¬μš© κ°€λŠ₯ν•˜μ§€λ§Œ, 클래슀둜 μ„ μ–Έν•˜λ©΄ νƒ€μž…κ³Ό κ°’μœΌλ‘œ λͺ¨λ‘ μ‚¬μš©ν•  수 μžˆλ‹€. (νƒ€μž…κ³Ό κ°’μœΌλ‘œ μ°Έμ‘°)
class Square {
  constructor(public width: number) {}
}
class Rectangle extends Square {
  constructor(public width: number, public height: number) {
    super(width);
  }
}
type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  // νƒ€μž…μœΌλ‘œ μ°Έμ‘°
  if (shape instanceof Rectangle) {
    // κ°’μœΌλ‘œ μ°Έμ‘°
    shape; // Type is Rectangle
    return shape.width * shape.height;
  } else {
    shape; // Type is Square
    return shape.width * shape.width; // OK
  }
}

νƒ€μž… 연산은 λŸ°νƒ€μž„μ— 영ν–₯을 주지 μ•ŠλŠ”λ‹€.

// ts μ½”λ“œ
function asNumber(val: number | string): number {
  return val as number; // νƒ€μž… 단언
}
// λ³€ν™˜λœ js
function asNumber(val) {
  return val; // νƒ€μž… 단언은 νƒ€μž… 연산이고, λŸ°νƒ€μž„ λ™μž‘μ— μ•„λ¬΄λŸ° 영ν–₯을 λ―ΈμΉ˜μ§€ μ•ŠμœΌλ―€λ‘œ, 값을 μ •μ œν•˜κΈ° μœ„ν•΄μ„œλŠ” λŸ°νƒ€μž„μ˜ νƒ€μž…μ„ μ²΄ν¬ν•˜κ³ , μžλ°”μŠ€ν¬λ¦½νŠΈ 연산을 톡해 λ³€ν™˜μ„ μˆ˜ν–‰ν•΄μ•Ό ν•œλ‹€.
}

λŸ°νƒ€μž„ νƒ€μž…μ€ μ„ μ–Έλœ νƒ€μž…κ³Ό λ‹€λ₯Ό 수 μžˆλ‹€.

  • νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” Dead Code 죽은 μ½”λ“œλ₯Ό μ°Ύμ•„λ‚Ό 수 μžˆλ‹€.
    • μ˜ˆμ™Έ: api response

νƒ€μž…μŠ€ν¬λ¦½νŠΈ νƒ€μž…μœΌλ‘œλŠ” ν•¨μˆ˜λ₯Ό μ˜€λ²„λ‘œλ“œ ν•  수 μ—†λ‹€.

  • C++ 같은 μ–Έμ–΄μ—μ„œλŠ” ν•¨μˆ˜ μ˜€λ²„λ‘œλ”©μ΄ κ°€λŠ₯함
    • νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” μ˜€λ²„λ‘œλ”© μ•ˆλ¨
function add(a: number, b: number) {
  //     ~~~ Duplicate function implementation
  return a + b;
}
function add(a: string, b: string) {
  //     ~~~ Duplicate function implementation
  return a + b;
}
  • {"noImplicitAny":false}, νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” ν•¨μˆ˜ μ˜€λ²„λ‘œλ”© κΈ°λŠ₯이 μžˆμ§€λ§Œ νƒ€μž… μˆ˜μ€€μ—μ„œλ§Œ λ™μž‘
    • 선언문이 μ—¬λŸ¬κ°œμ—¬λ„ implementation (κ΅¬ν˜„μ²΄)λŠ” 였직 ν•˜λ‚˜
// tsConfig: {"noImplicitAny":false}

function add(a: number, b: number): number; // μ–˜κ°€ μ§„μ§œ
function add(a: string, b: string): string;

function add(a, b) {
  return a + b;
}

const three = add(1, 2); // Type is number
const twelve = add("1", "2"); // Type is string

νƒ€μž…μŠ€ν¬λ¦½νŠΈ νƒ€μž…μ€ λŸ°νƒ€μž„ μ„±λŠ₯에 영ν–₯을 주지 μ•ŠλŠ”λ‹€.

  • νƒ€μž…κ³Ό νƒ€μž… μ—°μ‚°μžλŠ” JS λ³€ν™˜ μ‹œμ μ— 제거되기 λ•Œλ¬Έμ—, λŸ°νƒ€μž„ μ„±λŠ₯에 영ν–₯을 주지 μ•ŠλŠ”λ‹€.
  • νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ 정적 νƒ€μž…μ€ λΉ„μš©μ΄ μ „ν˜€ 듀지 μ•ŠλŠ”λ‹€.

νŠΉμ§•

  • 'λŸ°νƒ€μž„' μ˜€λ²„ν—€λ“œκ°€ μ—†λŠ” λŒ€μ‹ , νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ»΄νŒŒμΌλŸ¬λŠ” 'λΉŒλ“œνƒ€μž„' μ˜€λ²„ν—€λ“œκ°€ μžˆλ‹€.
    • 컴파일 속도가 빠름: 증뢄(incremental) λΉŒλ“œμ‹œ 체감
    • μ˜€λ²„ν—€λ“œκ°€ 컀지면, λΉŒλ“œ λ„κ΅¬μ—μ„œ transpile only μ„€μ •(νƒ€μž… 체크λ₯Ό κ±΄λ„ˆλœ€)
  • ν˜Έν™˜μ„±κ³Ό μ„±λŠ₯ μ‚¬μ΄μ˜ 선택
    • ν˜Έν™˜μ„±: 였래된 λŸ°νƒ€μž„ ν™˜κ²½μ„ μ§€μ›ν•˜κΈ° μœ„ν•΄ μ„±λŠ₯ μ˜€λ²„ν—€λ“œ κ°μ•ˆ
      • ex) ES5 νƒ€κΉƒμœΌλ‘œ 컴파일 μ‹œ, νŠΉμ • 헬퍼 μ½”λ“œ μΆ”κ°€
    • μ„±λŠ₯: μ„±λŠ₯ μ€‘μ‹¬μ˜ λ„€μ΄ν‹°λΈŒ κ΅¬ν˜„μ²΄ 선택

μš”μ•½

  • μ½”λ“œ 생성은 νƒ€μž… μ‹œμŠ€ν…œκ³Ό λ¬΄κ΄€ν•˜λ‹€.
    • λŸ°νƒ€μž„ λ™μž‘μ΄λ‚˜ μ„±λŠ₯에 영ν–₯을 주지 μ•ŠλŠ”λ‹€.
  • νƒ€μž… 였λ₯˜κ°€ μ‘΄μž¬ν•˜λ”λΌλ„ μ½”λ“œ 생성(컴파일) κ°€λŠ₯ (noEmitOnError둜 막기 κ°€λŠ₯)
  • νƒ€μž…μ€ λŸ°νƒ€μž„μ— μ‚¬μš©ν•  수 μ—†λ‹€.
    • λŸ°νƒ€μž„μ— νƒ€μž…μ„ μ§€μ •ν•˜λ €λ©΄, νƒ€μž… 정보 μœ μ§€λ₯Ό μœ„ν•œ λ³„λ„μ˜ 방법
      • ex) νƒœκ·Έλœ μœ λ‹ˆμ˜¨, 속성 체크, 클래슀

μ•„μ΄ν…œ 4: ꡬ쑰적 타이핑 μ΅μˆ™ν•΄μ§€κΈ° - Stuctural

  • μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” duck typing
    • ν•¨μˆ˜μ˜ λ§€κ°œλ³€μˆ˜ 값이 λͺ¨λ‘ μ œλŒ€λ‘œ 주어진닀면, 값이 μ–΄λ–»κ²Œ λ§Œλ“€μ–΄μ‘ŒλŠ”μ§€ 신경쓰지 μ•Šκ³  μ‚¬μš©
  • νƒ€μž…μŠ€ν¬λ¦½νŠΈλ„ λ™μž‘, λ§€κ°œλ³€μˆ˜ 값이 μš”κ΅¬μ‚¬ν•­μ„ λ§Œμ‘±ν•œλ‹€λ©΄ νƒ€μž…μ΄ 무엇인지 신경쓰지 μ•ŠλŠ” λ™μž‘μ„ κ·ΈλŒ€λ‘œ λͺ¨λΈλ§ν•œλ‹€.
    • λ‹€λ§Œ, νƒ€μž… 체컀의 νƒ€μž…μ— λŒ€ν•œ 이해도가 μ‚¬λžŒκ³Ό 닀름
      • ꡬ쑰적 타이핑을 μ œλŒ€λ‘œ μ΄ν•΄ν•œλ‹€λ©΄ 였λ₯˜μ™€ 였λ₯˜κ°€ μ•„λ‹Œ 경우의 차이λ₯Ό μ•Œκ³  κ²¬κ³ ν•œ μ½”λ“œ μž‘μ„± κ°€λŠ₯
interface Vector2D {
  x: number;
  y: number;
}
function calculateLength(v: Vector2D) {
  return Math.sqrt(v.x * v.x + v.y * v.y);
}
interface NamedVector {
  name: string;
  x: number;
  y: number;
}
const v: NamedVector = { x: 3, y: 4, name: "Zee" };
calculateLength(v); // OK, result is 5
// Vector2D, NamedVector μ‚¬μ΄μ˜ 관계λ₯Ό μ„ μ–Έν•˜μ§€ μ•Šμ•˜λ‹€
// NamedVectorκ°€ Vector2D와 ν˜Έν™˜λ˜λŠ” ꡬ쑰이기 λ•Œλ¬Έμ—, calculateLength 호좜이 κ°€λŠ₯ν•˜λ‹€ => ꡬ쑰적 타이핑

ꡬ쑰적 νƒ€μ΄ν•‘μœΌλ‘œ λ°œμƒν•œ 문제 경우

interface Vector2D {
  x: number;
  y: number;
}
function calculateLength(v: Vector2D) {
  return Math.sqrt(v.x * v.x + v.y * v.y);
}
interface NamedVector {
  name: string;
  x: number;
  y: number;
}
interface Vector3D {
  x: number;
  y: number;
  z: number;
}
function normalize(v: Vector3D) {
  const length = calculateLength(v); // 2D벑터λ₯Ό 받도둝 μ„ μ–Έλœ ν•¨μˆ˜μ—μ„œ 3D벑터λ₯Ό λ°›μ•˜μŒμ—λ„ νƒ€μž… 체컀가 문제둜 μΈμ‹ν•˜μ§€ μ•ŠμŒ
  return {
    x: v.x / length,
    y: v.y / length,
    z: v.z / length,
  };
} // κ²°κ΅­ 1둜 normalizeλ˜μ§€ μ•Šμ•˜λ‹€.

νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ νƒ€μž… μ‹œμŠ€ν…œμ€ μ—΄λ € μžˆλ‹€(open)

  • νƒ€μž…μ΄ ν™•μž₯에 μ—΄λ €μžˆλ‹€. νƒ€μž…μ— μ„ μ–Έλœ 속성 외에 μž„μ˜μ˜ 속성을 μΆ”κ°€ν•˜λ”λΌλ„ 였λ₯˜κ°€ λ°œμƒν•˜μ§€ μ•ŠλŠ”λ‹€.
function calculateLengthL1(v: Vector3D) {
  let length = 0;
  for (const axis of Object.keys(v)) {
    const coord = v[axis];
    //   ~~~~~~~ Element implicitly has an 'any' type because ...
    //           'string' can't be used to index type 'Vector3D'
    length += Math.abs(coord);
  }
  return length;
}

const vec3D = { x: 3, y: 4, z: 1, address: "123 Broadway" };
// 숫자 μ•„λ‹Œ 값을 넣어도, ν˜Έν™˜λ˜λŠ” ꡬ쑰둜 ν•¨μˆ˜ μ‹€ν–‰ (ꡬ쑰적 타이핑)
calculateLengthL1(vec3D); // OK, returns NaN
  • vλŠ” μ–΄λ–€ 속성이든지 κ°€μ§ˆ 수 있기 λ•Œλ¬Έμ—, v[axis]κ°€ number라고 ν™•μ •ν•  수 μ—†λ‹€.
    • μ •ν™•ν•œ νƒ€μž…μœΌλ‘œ 객체λ₯Ό μˆœνšŒν•˜κΈ° κΉŒλ‹€λ‘œμ›€
function calculateLengthL1(v: Vector3D) {
  return Math.abs(v.x) + Math.abs(v.y) + Math.abs(v.z); // 루프보닀 이게 λ‚˜μŒ (μ•„μ΄ν…œ 54μ—μ„œ λ‹€λ£° 것)
}

클래슀의 경우

class C {
  foo: string;
  constructor(foo: string) {
    this.foo = foo;
  }
}

const c = new C("instance of C");
const d: C = { foo: "object literal" }; // OK!
// ꡬ쑰적으둜, ν•„μš”ν•œ 속성과 μƒμ„±μžκ°€ μ‘΄μž¬ν•˜κΈ° λ•Œλ¬Έμ— λ¬Έμ œκ°€ μ—†λ‹€
// λ‹¨μˆœ 할당이 μ•„λ‹Œ μ—°μ‚° 둜직이 μ‘΄μž¬ν•œλ‹€λ©΄? d의 경우 μƒμ„±μžλ₯Ό μ‹€ν–‰ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ λ¬Έμ œκ°€ λ°œμƒν•˜κ²Œ λœλ‹€.

// 1️⃣ λ©”μ„œλ“œκ°€ μžˆμ„ 경우, ꡬ쑰적으둜 닀름
const d: C = { foo: "object literal" };
//    ~ Property 'hi' is missing in type '{ foo: string; }' but required in type 'C'.

// 2️⃣ λ‹¨μˆœ 할당이 μ•„λ‹ˆλΌ λ¬Έμ œκ°€ μƒκΈ°λŠ” 경우
class C {
  foo: string;
  constructor(foo: string) {
    this.foo = foo + " with bar";
  }
}

const c = new C("instance of C");
const d: C = { foo: "object literal" };

console.log(c.foo); // "instance of C with bar"
console.log(d.foo); // "object literal"

ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•  λ•Œ

  • DB에 μΏΌλ¦¬ν•˜κ³  κ²°κ³Όλ₯Ό μ²˜λ¦¬ν•˜λŠ” ν•¨μˆ˜
interface PostgresDB {
  runQuery: (sql: string) => any[];
}
interface Author {
  first: string;
  last: string;
}
interface DB {
  runQuery: (sql: string) => any[];
}
function getAuthors(database: DB): Author[] {
  const authorRows = database.runQuery(`SELECT FIRST, LAST FROM AUTHORS`);
  return authorRows.map((row) => ({ first: row[0], last: row[1] }));
}
// test μ‹œ 용이
// 좔상화(DB)ν•΄μ„œ, 둜직과 ν…ŒμŠ€νŠΈλ₯Ό νŠΉμ •ν•œ κ΅¬ν˜„(PostgresDB)μœΌλ‘œλΆ€ν„° 뢄리함
// 라이브러리 κ°„μ˜ μ˜μ‘΄μ„±μ„ μ™„λ²½νžˆ 뢄리할 수 μžˆλ‹€
test("getAuthors", () => {
  const authors = getAuthors({
    runQuery(sql: string) {
      return [
        ["Toni", "Morrison"],
        ["Maya", "Angelou"],
      ];
    },
  });
  expect(authors).toEqual([
    { first: "Toni", last: "Morrison" },
    { first: "Maya", last: "Angelou" },
  ]);
});

μš”μ•½

  • μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” 덕 타이핑 기반
    • νƒ€μž…μŠ€ν¬λ¦½νŠΈκ°€ 이λ₯Ό λͺ¨λΈλ§ν•˜κΈ° μœ„ν•΄ ꡬ쑰적 타이핑을 μ‚¬μš©ν•¨μ„ 이해해야 ν•œλ‹€.
    • μ–΄λ–€ μΈν„°νŽ˜μ΄μŠ€μ— ν• λ‹Ή κ°€λŠ₯ν•œ 값이라면, νƒ€μž… 선언에 λͺ…μ‹œμ μœΌλ‘œ λ‚˜μ—΄λœ 속성듀을 가지고 μžˆμ„ 것. (ꡬ쑰적으둜 ν˜Έν™˜)
    • νƒ€μž…μ€ λ΄‰μΈλ˜μ–΄μžˆμ§€ μ•Šλ‹€
  • ν΄λž˜μŠ€λ„ ꡬ쑰적 타이핑 κ·œμΉ™μ„ λ”°λ₯Έλ‹€. μΈμŠ€ν„΄μŠ€κ°€ μ˜ˆμƒκ³Ό λ‹€λ₯Ό 수 μžˆλ‹€.
  • ꡬ쑰적 타이핑을 μ‚¬μš©ν•˜λ©΄ μœ λ‹› ν…ŒμŠ€νŒ…μ΄ μš©μ΄ν•˜λ‹€.

μ•„μ΄ν…œ 5: any νƒ€μž… μ§€μ–‘ν•˜κΈ° - Any

  • νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ νƒ€μž… μ‹œμŠ€ν…œμ€ 점진적(gradual)이고 선택적(optional)
    • 점진적: μ½”λ“œμ— νƒ€μž…μ„ μ‘°κΈˆμ”© μΆ”κ°€ν•  수 μžˆλ‹€.
    • 선택적: μ–Έμ œλ“ μ§€ νƒ€μž… 체컀λ₯Ό ν•΄μ œν•  수 μžˆλ‹€.
let age: number;
age = "12";
// ~~~ Type '"12"' is not assignable to type 'number'
age = "12" as any; // OK

any νƒ€μž…μ—λŠ” νƒ€μž… μ•ˆμ •μ„±μ΄ μ—†λ‹€.

  • 혼돈!
let age: number;
age = "12" as any;

age += 1; // OK; at runtime, age is now "121"

anyλŠ” ν•¨μˆ˜ contract (μ‹œκ·Έλ‹ˆμ²˜)λ₯Ό λ¬΄μ‹œν•œλ‹€.

  • ν•¨μˆ˜
    • ν˜ΈμΆœν•˜λŠ” μͺ½μ€ μ•½μ†λœ νƒ€μž…μ˜ μž…λ ₯을 제곡
    • ν•¨μˆ˜λŠ” μ•½μ†λœ νƒ€μž…μ˜ 좜λ ₯ λ°˜ν™˜
function calculateAge(birthDate: Date): number {
  // birthDateλŠ” Date μ•„λ‹˜!
  ...
}

let birthDate: any = "1990-01-19";
calculateAge(birthDate); // OK
  • μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” μ’…μ’… μ•”μ‹œμ μœΌλ‘œ νƒ€μž…μ΄ λ³€ν™˜λ˜κΈ° λ•Œλ¬Έμ—, string νƒ€μž…μ΄ number νƒ€μž…μ΄ ν•„μš”ν•œ κ³³μ—μ„œ 였λ₯˜ 없이 싀행될 λ•Œκ°€ 있고, 그럴 경우 문제 λ°œμƒ

any νƒ€μž…μ—λŠ” μ–Έμ–΄ μ„œλΉ„μŠ€κ°€ μ μš©λ˜μ§€ μ•ŠλŠ”λ‹€.

  • νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ–Έμ–΄ μ„œλΉ„μŠ€
    • μ‹¬λ²Œμ— νƒ€μž…μ΄ μžˆλ‹€λ©΄, μžλ™μ™„μ„±κ³Ό μ μ ˆν•œ 도움말이 제곡됨 image
    • Rename Symbol μ•ˆλ¨
  • νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ λͺ¨ν†  "ν™•μž₯ κ°€λŠ₯ν•œ μžλ°”μŠ€ν¬λ¦½νŠΈ": 생산성 ν–₯상!

any νƒ€μž…μ€ μ½”λ“œ λ¦¬νŒ©ν„°λ§ λ•Œ 버그λ₯Ό κ°μΆ˜λ‹€.

  • νƒ€μž… 체컀λ₯Ό 톡과함에도, λŸ°νƒ€μž„μ— 였λ₯˜κ°€ λ°œμƒν•œλ‹€.
    • anyκ°€ μ•„λ‹Œ ꡬ체적인 νƒ€μž…μ„ μ‚¬μš©ν–ˆλ‹€λ©΄, 였λ₯˜λ₯Ό λ°œκ²¬ν–ˆμ„ 것
interface ComponentProps {
  onSelectItem: (item: any) => void;
}
function renderSelector(props: ComponentProps) {
  /* ... */
}

let selectedId: number = 0;
function handleSelectItem(item: any) {
  selectedId = item.id;
}

renderSelector({ onSelectItem: handleSelectItem });

// ComponentPropsλ₯Ό μ•„λž˜ μ½”λ“œλ‘œ 바꿨을 λ•Œ, handleSelectItem의 였λ₯˜λ₯Ό λŸ°νƒ€μž„κΉŒμ§€ μ•Œμ•„λ³Ό 수 μ—†λ‹€. νƒ€μž… 체컀λ₯Ό ν†΅κ³Όν•˜κΈ° λ•Œλ¬Έ.
interface ComponentProps {
  onSelectItem: (id: number) => void;
}

anyλŠ” νƒ€μž… 섀계λ₯Ό κ°μΆ˜λ‹€

  • 객체λ₯Ό μ •μ˜ν•  λ•Œ 특히 문제! μ“°μ§€λ§ˆμ„Έμš”
    • μƒνƒœ 객체의 섀계λ₯Ό 감좰버리기 λ•Œλ¬Έ
  • κΉ”λ”ν•˜κ³  μ •ν™•ν•˜κ³  λͺ…λ£Œν•œ μ½”λ“œ μž‘μ„±μ„ μœ„ν•΄ νƒ€μž… μ„€κ³„λŠ” ν•„μˆ˜λ‹€
    • λ™λ£Œκ°€ μ½”λ“œ κ²€ν†  μ‹œ, μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μƒνƒœλ₯Ό μ–΄λ–»κ²Œ λ³€κ²½ν–ˆλŠ”μ§€ μ½”λ“œλΆ€ν„° μž¬κ΅¬μ„±ν•΄μ•Ό 함
    • 섀계가 λͺ…ν™•νžˆ 보이도둝 νƒ€μž…μ„ μž‘μ„±ν•˜μž

anyλŠ” νƒ€μž… μ‹œμŠ€ν…œμ˜ 신뒰도λ₯Ό λ–¨μ–΄λœ¨λ¦°λ‹€

  • νƒ€μž… 체컀가 휴먼 μ—λŸ¬λ₯Ό μž‘μ•„μ£Όκ³ , μ½”λ“œμ˜ 신뒰도λ₯Ό λ†’μž„
    • λŸ°νƒ€μž„μ— νƒ€μž… 였λ₯˜κ°€ 생긴닀면? νƒ€μž… 체컀λ₯Ό μ‹ λ’°ν•  수 없을 것
  • any νƒ€μž…μ΄ λ§Žλ‹€λ©΄? 골치 μ•„ν””
    • νƒ€μž… 였λ₯˜ μˆ˜μ •
    • μ‹€μ œ νƒ€μž… κΈ°μ–΅

μš”μ•½

  • any νƒ€μž…μ„ μ‚¬μš©ν•˜λ©΄, νƒ€μž… 체컀와 νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ–Έμ–΄λ₯Ό 무λ ₯ν™”
  • μ§„μ§œ λ¬Έμ œμ μ„ 감좔고, 개발 κ²½ν—˜μ„ μ•…ν™”μ‹œν‚€κ³ , νƒ€μž… μ‹œμŠ€ν…œμ˜ 신뒰도λ₯Ό λ–¨μ–΄λœ¨λ¦°λ‹€.
  • 쓰지 λ§ˆμ„Έμš”~