🌆
Sunset Archive
GitHubLinkedInBlog
  • litsynp
  • Archives
    • Projects
      • Let's Parking!
      • 맛집몬스터 (JMT Monster)
      • spring-x-demo Projects
    • Articles
      • My Thoughts on GitBook
      • Wake-on-LAN & RDP Setup
    • Learning
      • Languages
        • Rust
          • Useful Links
          • Syntax
          • Pointers
          • Dependencies
          • Logging
          • Phantom Types
          • Iterable
            • Optional Skip
        • Go
          • Useful Links
          • Structures
          • Zero Values
          • Error Handling
          • Dependency Injection
          • Project Layout
        • JavaScript/TypeScript
          • Basics
            • Types
              • [JS] Falsy Values
              • [TS] Types - unknown, any, never
            • Rest Parameters (...)
            • Spread Syntax (...)
            • Destructuing Assignment
            • CJS, AMD, UMD, ESM
          • Advanced
            • Conditional Destructuring Assignment
            • Type Guards
          • Miscellaneous
            • Dependency Injection in JS?
            • ESLint, Prettier, TypeScript
          • Node
            • Useful Links
            • General
              • V8 Engine
              • Version Management: NVM
              • Environment Variables
            • Database
              • Knex
        • C
          • Dynamic Multi-dimensional Arrays
        • Spring
          • General
            • @Import v.s. @ContextConfiguration
            • MessageSource
          • Kotlin+Spring
            • Kotlin Annotations (Use-Site Targets)
            • Handling Null in Request DTO
            • Handling Null in URL
          • Reactive Stack
            • Reactive API with Spring WebFlux
          • Spring Security
            • Google OAuth Setup
          • Spring Batch
            • Bulk Insert
        • Kotlin
          • val/var in Constructor
          • Initializer Blocks
          • Inheritance Modifiers (final, open, abstract)
          • Delegate Pattern
        • Java
          • Serialization
          • Random Number Generation
            • (1) Math.random() v.s. Random
            • (2) Random v.s. ThreadLocalRandom
        • Python
          • Version Management
        • Ruby
          • Installation
          • Getters & Setters
        • Elixir
        • Erlang
        • Flutter
        • AWS
          • AWS CLI
        • Terraform
          • Installation
          • Basics
      • Code
        • OOP
          • The 4 Basic Concepts of OOP
          • The SOLID Principles
          • GRASP
          • Spring POJO
          • Others
        • Functional Programming
          • Currying
          • Higher-Order Function
          • Closure
          • Monad
        • 공변성, 반공변성, 무공변성
        • Others
          • UUID
          • GraphQL
          • Multimedia
            • Streaming
          • Geography
            • 위도 (Latitude), 경도 (Longitude)
      • Tools
        • Nix
        • Mermaid
          • Flowchart
          • Sequence Diagram
          • Class Diagram
          • Entity Relationship Diagrams
        • VSCode
          • VSCode CLI
          • VSCode Extensions
        • JetBrains
          • IntelliJ - Open Projects in Tabs
          • Delete Leftover IDE Directories
        • vim
          • Commands
      • Books
        • 다재다능 코틀린 프로그래밍 (Programming Kotlin)
          • Pt 01. 코틀린으로 스크립팅 하기
            • Ch 01. 코틀린 시작하기
            • Ch 02. Java 개발자를 위한 코틀린 필수 사항
            • Ch 03. 함수를 사용하자
            • Ch 05. 콜렉션 사용하기
        • 오브젝트 (Object)
          • Ch.0 들어가며 - 프로그래밍 패러다임
          • Ch.1 객체, 설계
          • Ch.2 객체지향 프로그래밍
          • Ch.3 역할, 책임, 협력
          • Ch.4 설계 품질과 트레이드오프
          • Ch.5 책임 할당하기
          • Ch.6 메시지와 인터페이스
          • Ch.7 유연한 설계
          • Ch.8 의존성 관리하기
          • Ch.9 유연한 설계
          • Ch.10 상속과 코드 재사용
          • Ch.11 합성과 유연한 설계
          • Ch.12 다형성
          • Ch.13 서브클래싱과 서브타이핑
          • Ch.14 일관성 있는 협력
          • Ch.15 디자인 패턴과 프레임워크
          • End. 마치며 - 나아가기
          • 후기
        • 헤드 퍼스트 디자인 패턴 (Head First Design Patterns)
          • Ch.1 디자인 패턴 소개와 전략 패턴
          • Ch.2 옵저버 패턴
          • Ch.3 데코레이터 패턴
          • Ch.4 팩토리 패턴
          • Ch.5 싱글턴 패턴
          • Ch.6 커맨드 패턴
          • Ch.7 어댑터 패턴과 퍼사드 패턴
          • Ch.8 템플릿 메소드 패턴
          • Ch.9 반복자 패턴과 컴포지트 패턴
          • Ch.10 상태 패턴
          • Ch.11 프록시 패턴
          • Ch.12 복합 패턴
        • 이펙티브 타입스크립트 (Effective TypeScript)
          • Ch.1 타입스크립트 알아보기
            • Item 1 타입스크립트와 자바스크립트의 관계 이해하기
            • Item 2 타입스크립트 설정 이해하기
            • Item 3 코드 생성과 타입이 관계없음을 이해하기
            • Item 4 구조적 타이핑에 익숙해지기
            • Item 5 any 타입 지양하기
          • Ch.2 타입스크립트의 타입 시스템
            • Item 6 편집기를 사용하여 타입 시스템 탐색하기
            • Item 7 타입이 값들의 집합이라고 생각하기
            • Item 1-7 Study Summary
            • Item 8 타입 공간과 값 공간의 심벌 구분하기
            • Item 9 타입 단언보다는 타입 선언을 사용하기
            • Item 10 객체 래퍼 타입 피하기
            • Item 11 잉여 속성 체크의 한계 인지하기
            • Item 12 함수 표현식에 타입 적용하기
            • Item 13 타입과 인터페이스의 차이점 알기
            • Item 14 타입 연산과 제너릭 사용으로 반복 줄이기
            • Item 15 동적 데이터에 인덱스 시그니처 사용하기
            • Item 16 number 인덱스 시그니처보다는 Array, 튜플, ArrayLike를 사용하기
            • Item 17 변경 관련된 오류 방지를 위해 readonly 사용하기
            • Item 18 매핑된 타입을 사용하여 값을 동기화하기
          • Ch.3 타입 추론
            • Item 19 추론 가능한 타입을 사용해 장황한 코드 방지하기
            • Item 20 다른 타입에는 다른 변수 사용하기
            • Item 21 타입 넓히기
            • Item 22 타입 좁히기
            • Item 23 한꺼번에 객체 생성하기
            • Item 24 일관성 있는 별칭 사용하기
            • Item 25 비동기 코드에는 콜백 대신 async 함수 사용하기
            • Item 26 타입 추론에 문맥이 어떻게 사용되는지 이해하기
            • Item 27 함수형 기법과 라이브러리로 타입 흐름 유지하기
          • Ch.4 타입 설계
            • Item 28 유효한 상태만 표현하는 타입을 지향하기
            • Item 29 사용할 때는 너그럽게, 생성할 때는 엄격하게
            • Item 30 문서에 타입 정보를 쓰지 않기
            • Item 31 타입 주변에 null 값 배치하기
            • Item 32 유니온의 인터페이스보다는 인터페이스의 유니온을 사용하기
            • Item 33 string 타입보다 더 구체적인 타입 사용하기
            • Item 34 부정확한 타입보다는 미완성 타입을 사용하기
            • Item 35 데이터가 아닌, API와 명세를 보고 타입 만들기
            • Item 36 해당 분야의 용어로 타입 이름 짓기
            • Item 37 공식 명칭에는 상표를 붙이기
          • Ch.5 any 다루기
            • Item 38 any 타입은 가능한 한 좁은 범위에서만 사용하기
            • Item 39 any를 구체적으로 변형해서 사용하기
            • Item 40 함수 안으로 타입 단언문 감추기
            • Item 41 any의 진화를 이해하기
            • Item 42 모르는 타입의 값에는 any 대신 unknown을 사용하기
            • Item 43 몽키 패치보다는 안전한 타입을 사용하기
            • Item 44 타입 커버리지를 추적하여 타입 안전성 유지하기
          • Ch.6 타입 선언과 @types
            • Item 45 devDependencies에 typescript와 @types 추가하기
            • Item 46 타입 선언과 관련된 세 가지 버전 이해하기
            • Item 47 공개 API에 등장하는 모든 타입을 익스포트하기
            • Item 48 API 주석에 TSDoc 사용하기
            • Item 49 콜백에서 this에 대한 타입 제공하기
            • Item 50 오버로딩 타입보다는 조건부 타입을 사용하기
            • Item 51 의존성 분리를 위해 미러 타입 사용하기
            • Item 52 테스팅 타입의 함정에 주의하기
          • Ch.7 코드를 작성하고 실행하기
            • Item 53 타입스크립트 기능보다는 ECMAScript 기능을 사용하기
            • Item 54 객체를 순회하는 노하우
            • Item 55 DOM 계층 구조 이해하기
            • Item 56 정보를 감추는 목적으로 private 사용하지 않기
            • Item 57 소스맵을 사용하여 타입스크립트 디버깅하기
          • Ch.8 타입스크립트로 마이그레이션하기
            • Item 58 모던 자바스크립트로 작성하기
            • Item 59 타입스크립트 도입 전에 @ts-check와 JSDoc으로 시험해 보기
            • Item 60 allowJS로 타입스크립트와 자바스크립트 같이 사용하기
            • Item 61 의존성 관계에 따라 모듈 단위로 전환하기
            • Item 62 마이그레이션의 완성을 위해 noImplicitAny 설정하기
        • Dive Into Design Patterns
          • 디자인 패턴 소개
          • 소프트웨어 디자인 원칙들
          • 디자인 패턴 목록
          • 유용한 링크
        • 가상 면접 사례로 배우는 대규모 시스템 설계 기초 (System Design Interview)
          • Key Points
          • Real Life Systems
          • Engineering Blogs
        • Node.js 디자인 패턴 바이블 (Node.js Design Patterns 3rd Edition)
        • 리팩터링 2판 (Refactoring: 2nd Ed.)
          • 1장 리팩터링: 첫 번째 예시
          • 2장 리팩터링 원칙
          • 3장 코드에서 나는 악취
          • 4장 테스트 구축하기
Powered by GitBook
On this page
  • About
  • ECMAScript 모듈 사용하기
  • 프로토타입 대신 클래스 사용하기
  • var 대신 let/const 사용하기
  • for(;;) 대신 for-of 또는 배열 메서드 사용하기
  • 함수 표현식보다는 화살표 함수 사용하기
  • 단축 객체 표현과 구조 분해 할당 사용하기
  • 함수 매개변수 기본값 사용하기
  • 저수준 프로미스나 콜백 대신 async/await 사용하기
  • 연관 배열에 객체 대신 Map과 Set 사용하기
  • 타입스크립트에 use strict 넣지 않기
  • Summary

Was this helpful?

Edit on GitHub
  1. Archives
  2. Learning
  3. Books
  4. 이펙티브 타입스크립트 (Effective TypeScript)
  5. Ch.8 타입스크립트로 마이그레이션하기

Item 58 모던 자바스크립트로 작성하기

PreviousCh.8 타입스크립트로 마이그레이션하기NextItem 59 타입스크립트 도입 전에 @ts-check와 JSDoc으로 시험해 보기

Last updated 2 years ago

Was this helpful?

About

마이그레이션을 어디서부터 시작해야 할지 몰라 막막하다면 옛날 버전의 JS 코드를 최신 버전의 JS로 바꾸는 작업부터 시작해보면 좋다.

이번 Item 58은 모던 JS의 주요 기능 몇 가지를 간략히 다룬다.

ECMAScript 모듈 사용하기

ES2015부터는 임포트(import)와 익스포트(export)를 사용하는 ECMAScript가 표준이 되었다.

  • 만약 마이그레이션 대상인 JS 코드가 단일 파일이거나 비표준 모듈 시스템을 사용 중이라면 ES 모듈로 전환하는 것이 좋다.

  • ES 모듈 시스템을 사용하기 위해서 프로젝트 종류에 따라 웹팩(webpack)이나 ts-node 같은 도구가 필요한 경우도 있다. ES 모듈 시스템은 TS에서도 잘 동작하며, 모듈 단위로 전환할 수 있게 해 주기 때문에 점진적 마이그레이션이 원활해진다. ()

다음은 CommonJS 모듈 시스템을 이용한 전형적인 예제이다.

// CommonJS
// a.js
const b = require('./b')
console.log(b.name)

// b.js
const name = 'Module B'
module.exports = { name }

이를 ES 모듈로 표현하면 다음과 같다.

// ECMAScript module
// a.ts
import * as b from './b'
console.log(b.name)

// b.ts
export const name = 'Module B'

프로토타입 대신 클래스 사용하기

과거에는 JS에서 프로토타입 기반의 객체 모델을 사용했으나, 개발자들의 선호도에 따라 결국 ES2015에 class 키워드를 사용하는 클래스 기반 모델이 도입되었다.

  • 마이그레이션하려는 코드에서 단순한 객체를 다룰 때 프로토토타입을 사용하고 있었다면 클래스로 바꾸는 것이 좋다.

다음은 단순 객체를 프로토타입으로 구현한 예제이다.

function Person(first, last) {
  this.first = first
  this.last = last
}

Person.prototype.getName = function() {
  return this.first + ' ' + this.last
}

const marie = new Person('Marie', 'Curie')
const personName = marie.getName()

다음은 프로토타입 기반 객체를 클래스 기반 객체로 바꾼 예시이다.

class Person {
  first: string
  last: string
  
  constructor(first: string, last: string) {
    this.first = first
    this.last = last
  }
  
  getName() {
    return this.first + ' ' + this.last
  }
}

const marie = new Person('Marie', 'Curie')
const personName = marie.getName()

참고로 편집기에서 프로토타입 객체에 마우스를 올려 간단히 클래스 객체로 변환할 수 있다.

var 대신 let/const 사용하기

JS var 키워드의 스코프(scope) 규칙에 문제가 있다는 것은 널리 알려진 사실이다.

스코프 문제를 자세히 알지 못하더라도 var 대신 let/const를 사용하면 스코프 문제를 피할 수 있다. let과 const는 제대로 된 블록 스코프 규칙을 가지며, 개발자들이 일반적으로 기대하는 방식으로 동작한다.

만약 var 키워드를 let/const로 변경하면 일부 코드에서 TS가 문제를 표시할 수도 있다. 오류가 발생한 부분은 잠재적으로 스코프 문제가 존재하므로 수정해야 한다.

중첩된 함수 구문에도 var의 경우와 비슷한 스코프 문제가 존재한다. 예를 들어보면:

function foo() {
  bar()
  function bar() {
    console.log('hello')
  }
}

foo 함수를 호출하면 bar 함수의 정의가 호이스팅(hoisting)되어 가장 먼저 수행되기 때문에 bar 함수가 문제없이 호출되고 hello가 출력된다. 호이스팅은 실행 순서를 예상하기 어렵게 만들고 직관적이지 않다. 대신 함수 표현식(const bar = () => { ... })을 사용하여 호이스팅 문제를 피하는 것이 좋다.

for(;;) 대신 for-of 또는 배열 메서드 사용하기

과거에는 JS에서 배열을 순회할 때 C-style for 루프를 사용했다.

for (var i = 0; i < array.length; i++) {
  const el = array[i]
  // ...
}

모던 JS에는 for-of 루프가 존재한다.

for (const el of array) {
  // ...
}

for-of 루프는 코드가 짧고 인덱스 변수를 사용하지도 않기 때문에 실수를 줄일 수 있다.

인덱스 변수가 필요하면 다음과 같이 forEach 메서드를 사용하면 된다.

array.forEach((el, i) => {
  // ...
})

함수 표현식보다는 화살표 함수 사용하기

this 키워드는 JS에서 가장 어려운 개념 중 하나이다. 일반적인 변수들과는 다른 스코프 규칙을 갖기 때문이다.

일반적으로는 this가 클래스 인스턴스를 참조할거라 생각하지만, 다음 예제처럼 예상치 못한 결과가 나올 수 있다.

class Foo {
  method() [
    console.log(this)
    [1, 2].forEach(function(i) {
      console.log(this)
    })
  }
}
const f = new Foo()
f.method()
// strict 모드에서 Foo, undefined, undefined를 출력한다.
// non-strict 모드에서 Foo, window, window (!)를 출력한다.

대신 화살표 함수를 이용하면 상위 스코프의 this를 유지할 수 있다.

class Foo {
  method() [
    console.log(this)
    [1, 2].forEach((i) => {
      console.log(this)
    })
  }
}
const f = new Foo()
f.method()
// 항상 Foo, Foo, Foo를 출력한다.
  • 인라인(또는 콜백)에서는 일반 함수보다 화살표 함수가 더 직관적이며 코드도 간결해지기 때문에 가급적 화살표 함수를 사용하는 것이 좋다.

  • 컴파일러 옵션에 noImplicitThis(또는 strict)를 설정하면 TS가 this 바인딩 관련 오류도 표시해주므로 설정해주는 것이 좋다.

단축 객체 표현과 구조 분해 할당 사용하기

pt 객체를 생성하는 다음 코드가 있다.

const x = 1, y = 2, z = 3
const pt = {
  x: x,
  y: y,
  z: z,
}

변수와 객체 속성의 이름이 같다면, 간단하게 다음 코드처럼 작성할 수 있다.

const x = 1, y = 2, z = 3
const pt = { x, y, z }

화살표 함수 내에서 객체를 반환할 땐 소괄호로 감싸야 한다. 화살표 함수에서 함수의 구현부에는 블록이나 단일 표현식이 필요하기 때문에 소괄호로 감싸서 표현식으로 만들어 준 것이다.

['A', 'B', 'C'].map((char, idx) => ({ char, idx }))
// [ { char: 'A', idx: 0 }, { char: 'B', idx: 1 }, { char: 'C', idx: 2 } ]

객체의 속성 중 함수를 축약해 표현하는 방법은 다음과 같다.

const obj = {
  onClickLong: function (e) {
    // ...
  },
  onClickCompact(e) {
    // ...
  }
}
const props = obj.props
const a = props.a
const b = props.b

다음처럼 줄여서 작성이 가능하다.

const { props } = obj
const { a, b } = props

또는 한 단계 더 줄여서 이렇게도 가능하다.

const { props: { a, b } } = obj

참고로 a, b는 변수로 선언되지만 props는 변수 선언이 아니라는 것에 유의하자.

구조 분해 문법에서는 기본값을 지정하는 것도 가능하다. 다음은 if 구문으로 기본값을 지정하는 방식이다.

let { a } = obj.props
if (a === undefined) a = 'default'

이렇게 구조 분해 문법으로 기본값을 할당할 수 있다.

const { a = 'default' } = obj.props

배열에서도 구조 분해 문법이 가능하다. 배열을 튜플처럼 사용할 경우 특히 유용하다.

const point = [1, 2, 3]
const [x, y, z] = point
const [, a, b] = point  // 첫 번째 요소 무시

함수 매개변수에서도 가능하다.

const points = [
  [1, 2, 3],
  [4, 5, 6],
]
points.forEach(([x, y, z]) => console.log(x, y, z))

단축 객체 표현과 마찬가지로 객체 구조 분해를 사용하면 문법이 간결해지고 변수 사용 간 실수를 줄일 수 있으므로 적극 사용하자.

함수 매개변수 기본값 사용하기

JS의 모든 매개변수는 선택적(생략 가능)이며, 매개변수를 지정하지 않으면 undefined로 간주된다.

function log2(a, b) {
  console.log(a, b)
}
log2() // undefined undefined

옛날엔 매개변수의 기본값을 지정하고 싶을 때 다음 코드처럼 구현하곤 했다.

function parseNum(str, base) {
  base = base || 10
  return parseInt(str, base)
}

모던 JS에서는 매개변수의 기본값을 직접 지정할 수 있다.

function parseNum(str, base = 10) {
  return parseInt(str, base)
}
  • 이런 식으로 하면 코드가 간결해지고 base가 선택적 매개변수라는 것을 명시하는 효과도 준다.

저수준 프로미스나 콜백 대신 async/await 사용하기

요점은 다음과 같다.

  • async/await을 사용하면 코드가 간결해져서 버그와 실수를 방지한다.

  • 비동기 코드에 타입 정보가 전달되어 타입 추론을 가능하게 한다.

function getJSON(url: string) {
  return fetch(url).then((response) => response.json())
}

function getJSONCallback(url: string, cb: (result: unknown) => void) {
  // ...
}

위와 같이 콜백과 프로미스를 사용한 코드보다는, 아래의 async/await으로 작성한 코드가 더 깔끔하고 직관적이다.

async function getJSON(url: string) {
  const response = await fetch(url)
  return response.json()
}

연관 배열에 객체 대신 Map과 Set 사용하기

function countWords(text: string) {
  const counts: { [words: string]: number } = {}
  for (const word of text.split(/[\s,.]+/)) {
    counts[word] = 1 + (counts[word] || 0)
  }
  return counts
}

별 문제가 없어 보이는 코드지만, constructor이라는 문자열이 주어지면 문제가 발생한다.

console.log(countWords('Objects have a constructor'))

실행 결과는 다음과 같다.

{
  Objects: 1,
  have: 1,
  a: 1,
  constructor: "1function Object() { [native code] }"
}

constructor의 초깃값은 undefined가 아니라 Object.prototype에 있는 생성자 함수이다. 원치 않는 값일 뿐 아니라, 타입도 number가 아닌 string이다. 이런 문제를 방지하려면 Map을 사용하자.

function countWords(text: string) {
  const counts: Map<string, number> = {}
  for (const word of text.split(/[\s,.]+/)) {
    counts[word] = 1 + (counts[word] || 0)
  }
  return counts
}

타입스크립트에 use strict 넣지 않기

ES5에서는 버그가 될 수 있는 코드 패턴에 오류를 표시해주는 엄격 모드(strict)가 도입되었다. 코드의 제일 처음에 'use strict'를 넣으면 엄격 모드가 활성화된다.

그러나 TS에서 수행되는 안전성 검사(sanity check)가 엄격 모드보다 훨씬 더 엄격한 체크를 하므로, 이는 무의미하다.

  • 실제로 TS 컴파일러에 alwaysStrict(또는 strict) 옵션을 설정하면 엄격 모드로 코드를 파싱하고 생성하는 JS 코드에서 'use strict'가 추가된다.

  • 즉, TS 코드에는 'use strict'를 넣지 말고, alwaysStrict 설정을 사용해야 한다.

Summary

  • TS 개발 환경은 모던 JS도 실행할 수 있으므로 모던 JS 최신 기능을 적극 사용하는 것이 좋다. 코드 품질을 향상시킬 수 있고, TS의 타입 추론도 더 나아진다.

  • TS 개발 환경에서는 컴파일러와 언어 서비스를 통해 클래스, 구조 분해, async/await 같은 기능을 쉽게 배울 수 있다.

  • 'use strict'는 TS 컴파일러 수준에서 사용되므로 코드에서 제거해야 한다.

for-in 문법도 존재하지만 에서 설명했듯이 몇 가지 문제점이 있기 때문에 사용하지 않는 것이 좋다.

this 바인딩 관련 자세한 내용은 참고.

이쪽 코드가 더 간결하고 중복된 이름을 생략하므로 가독성이 좋고 실수가 적다().

Note: 를 켜면 린터가 잡아준다.

단축 객체 표현(compact object literal)의 반대는 이라고 한다. 다음 예제를 보자.

기본값을 기본으로 타입 추론이 가능해져서, TS로 마이그레이션 할 때 매개변수에 타입 구문을 쓰지 않아도 된다().

에서 설명했듯 콜백, 프로미스보다 async/await을 권장한다.

에서 객체의 인덱스 시그니처를 사용하는 방법을 다루었다. 인덱스 시그니처는 편리하지만, 몇 가지 문제점이 존재한다. 문자열 내의 단어 개수를 세는 함수를 예로 들어본다.

와 를 통해 최신 기능을 확인할 수 있다.

Item 61
Item 16
Item 49
Item 36
ESLint의 object-shorthand
객체 구조 분해(object destructuring)
Item 19
Item 25
Item 15
TC39의 GitHub repository
TS의 릴리스 노트