반디북
자바스크립트 + 리액트 디자인 패턴
Ch5
도은

최신 자바스크립트 생태계에서 사용되는 디자인 패턴을 살펴보자

5.1 애플리케이션 분리의 중요성

  • 자바스크립트는 애플리케이션을 모듈(module)이라는 단위로 쪼개기 가능
  • 모듈로 구성하여 의존성을 낮추어 유지보수를 용이하게 만든다.
  • 모듈을 이용해 효율적으로 구현하면, 변경이 다른 부분에 어떤 영향을 미칠 수 있을지 쉽게 확인 가능

5.2 모듈 가져오기와 내보내기

  • 모듈을 사용하면 각 기능에 맞는 독립적인 단위로 코드 분리가 가능

5.3 모듈 객체

  • 모듈을 객체로 가져오면 모듈 리소르를 깔끔하게 가져오는 것이 가능
  • namespace exports
import * as Staff from '/modules/staff.js';
 
export const oven = {
  makeCupcake(toppings) {
    Staff.baker.bake('cupcake', toppings);
  },
  makePastry(mSize) {
    Staff.pastryChef.make('pastry', type);
  },
};

5.4 외부 소스로부터 가져오는 모듈

  • ES2015부터는 외부 소스에서 가져오는 원격 모듈을 쉽게 가져오는 것이 가능
import { cakeFactory } from 'https://example.com/modules/cakeFactory.js';
 
cakeFactor.oven.makeCupcake('sprinkles');
cakeFactor.oven.makeMuffin('large');

5.5 정적으로 모듈 가져오기

  • 실행하기 전에 모듈을 다운로드하고 실행해야 한다.
  • 초기 페이지 로드 시 많은 코드를 미리 로드해야 하므로 성능에 문제가 생길 수 있다.
import { cakeFactory } from '/modules/cakeFactory.js';
// 미리 로드된 정적 가져오기
 
cakeFactor.oven.makeCupcake('sprinkles');
cakeFactor.oven.makeMuffin('large');

5.6 동적으로 모듈 가져오기

  • 초기에 모두 로드하기보다 필요한 시점에 로드하는 것이 더 이로울 때가 있다.
  • 지연 로딩(lazy-loading) 모듈을 사용하면 필요한 시점에 로드하는 것이 가능
  • import(url)는 요청된 모듈의 네임스페이스 객체에 대한 프로미스 객체를 반환
form.addEventListener('submit', (e) => {
  e.preventDefault();
  import('/modules/cakeFactory.js').then((module) => {
    // 가져온 모듈 사용하기
    module.oven.makeCupcate('sprinkles');
    module.oven.makeMuffin('large');
  });
});
  • 동적 가져오기는 await와 함께 사용할 수 있다.
let module = await import('/modules/cakeFactory.js');
  • 사용자 인터랙션에 반응하거나, 화면에 보이면 실행하기 등 자주 사용되는 패턴

5.6.1 사용자 상호작용에 따라 가져오기

  • 일부 기능은 사용자 상호작용할 때만 필요할 수 있다.
  • 예를 들어, 채팅 창, 비디오 등
  • 이런 기능들은 페이지 로드 시점에 필요한 게 아니니 사용자가 컴포넌트를 클릭하는 등 상호작용에 따라 로드되는 것이 좋다.
const btn = document.querySelector('button');
 
// 이벤트 리스너 안에서 동적 가져오기
btn.addEventListener('click', (e) => {
  e.preventDefault();
  import('lodash.sortby')
    .then((module) => module.default)
    .then(sortInput())
    .catch((err) => {
      console.log(err);
    });
});

5.6.2 화면에 보이면 가져오기

  • 많은 컴포넌트들이 스크롤 아래에서 보이고는 하는데
  • 이때 스크롤을 했을 때 동적 가져오기로 구현하면 좋다.
  • IntersectionObserver API를 사용하면 컴포넌트가 화면에 보이는지 감지 가능

5.8 모듈을 사용하면 생기는 이점

  1. 한 번만 실행된다
    • 기존 스크립트는 DOM에 추가될 때마다 실행되는 반면
    • 모듈 스크립트는 한 번만 실행된다. 즉, 동일한 모듈을 여러 곳에서 가져와도 다시 실행되지 않는다.
    • 자바스크립트 모듈을 사용하면 의존성 트리의 가장 내부에 위치한 모듈이 먼저 실행
    • 가장 내부에 위치한 모듈이 먼저 평가되고 여기에 의존하는 모듈에 접근할 수 있다.
  2. 자동으로 지연 로드된다
    • 즉시 로드되지 않기 위해 다른 스크립트 파일은 defer 속성을 붙여야하지만
    • 모듈은 자동으로 지연되어 로드된다.
    • 여기서 지연 로드란, HTML 파싱이 끝난 후에 스크립트를 실행하라는 의미
    • 즉, HTML 파싱을 멈추지 않고 같이 비동기로 로드한다.
    • HTML 파싱이 끝난 뒤에 스크립트를 실행한다.
  3. 유지보수와 재사용이 쉽다
    • 모듈은 다른 모듈에 영향을 주지 않고 독립적으로 실행될 수 있는 코드 조각으로 관리
  4. 네임스페이스를 제공한다
    • 모듈은 관련 변수와 상수를 위한 개별 스페이스를 생성
    • 글로벌 네임스페이스에 영향 X
  5. 사용하지 않는 코드를 제거한다
    • 트리 쉐이킹 지원 O

5.9 생성자, 게터, 세터를 가진 클래스

  • ES2015+에서는 클래스가 추가
class Cake {
  // 생성자 안에서 변수를 정의힌다.
  constructor(name, toppings, price, cakeSize) {
    this.name = name;
    this.cakeSize = cakeSize;
    this.toppings = toppings;
    this.price = price;
  }
  // ES2015 버전 이상에서는 모든 것을 함수로 만드는 것을 피하고자 새로운 식별자를 사용하려고 했다.
  addTopping(topping) {
    this.toppings.push(topping);
  }
  // 게터는 메서드 이름 앞에 넣어 사용한다.
  get allToppings() {
    return this.toppings;
  }
  get qualifiesForDiscount() {
    return this.price > 5;
  }
  // 세터도 메서드 이름 앞에 넣어 사용한다.
  set size(size) {
    if (size < 0) {
      throw new Error('Cake must be a valid size: ' + 'either small, medium or large');
    }
    this.cakeSize = size;
  }
}
// 사용 방법
let cake = new Cake('chocolate', ['chocolate chips'], 5, 'large');
  • 최신 자바스크립트에서는 클래스 내부 멤버를 비공개로 정의하는 것이 가능
  • 비공개 클래스 멤버는 선언된 클래스 내부에서만 사용할 수 있다.
class CookieWithPrivateField {
  #privateField;
}
 
class CookieWithPrivateMethod {
  #privateMethod() {
    return 'delicious cookies';
  }
}
  • static 키워드를 통해 정적 메서드와 프로퍼티 정의 가능
  • 정적 멤버는 클래스를 초기화하지 않고도 사용 가능
  • 주로 어떠한 설정이나 캐시 데이터를 보관하기 위해 사용
class Cookie {
  constructor(flavor) {
    this.flavor = flavor;
  }
  static brandName = 'Best Bakes';
  static discountPercent = 5;
}
 
console.log(Cookie.brandName); // Best Bakes

5.10 자바스크립트 프레임워크와 클래스

  • 리액트 Hooks는 클래스를 사용하지 않고도 리액트의 상태와 라이프사이클을 다룰 수 있도록 만들어졌다.
  • 그러나 여전히 클래스는 컴포넌트 개발에 사용되고 있으며, 웹 컴포넌트 같은 다양한 시도들이 컴포넌트 개발 과정에서 클래스를 기반으로 이루어지고 있다.

🤔 Web Component ?

-https://developer.mozilla.org/ko/docs/Web/API/Web_components (opens in a new tab)
-https://handhand.tistory.com/328 (opens in a new tab)

  • HTML/CSS/JS를 이용하여 재사용가능한 컴포넌트를 만들 수 있는 표준 방식
  • 커스텀 HTML 엘리먼트 생성 가능
class MyElement extends HTMLElement {
  constructor() {
    super();
  }
  connectedCallback() {}
  attributeChangedCallback(attrName, oldVal, newVal) {}
  // ...
}