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