들어가기에 앞서
https://www.patterns.dev/vanilla/module-pattern 에 있는 설명과 예시를 바탕으로 하여 관련 주제에 대해 이제까지 공부한 내용을 개인적으로 정리한 포스팅임을 밝힙니다.
장점
- 코드를 재사용 가능하면서도 작게 나눌 수 있게 해주어 유지보수하기 쉽다.
- 명시적으로 export한 값들만 외부에 노출되기 때문에 명시적으로 export하지 않은 변수들은 모듈 내에서만 사용함으로써 전역 스코프의 변수들과 이름이 충돌하는 문제를 줄일 수 있다.
- 모듈 패턴을 사용하면 코드의 일부분을 캡슐화할 수 있다. 이는 의도치 않은 전역 변수 할당을 예방할 수 있어 여러 의존 모듈을 사용하거나 네임스페이스를 사용할 때 안전하다.
필요 조건
모든 자바스크립트 런타임에서 ES2015의 모듈을 사용하려면 바벨과 같은 트랜스파일러가 필요하다.
먼저 딱 1년 전 이맘 때에 정리한 export와 import 키워드에 대한 포스팅을 참조하자.
위의 포스팅을 작성할 때는 미처 몰랐던 점들은 다음과 같다.
- 모듈 스코프에 어떤 변수나 함수가 존재하는데, 동일한 이름의 값을 import해야 하는 경우, as 키워드를 통해 named export한 값의 이름을 변경할 수 있다.
import { add as addValues } from './math.js'
- * 와 이름을 사용하는 것으로 모듈 내 default export를 포함하여 export하는 모든 것들을 한번에 import할 수 있다. export된 모든 것을 포함하는 객체 형태로 사용할 수 있다.
- 이 경우 해당 모듈이 export하는 모든 것을 가져오기 때문에 불필요한 것들이 딸려오지 않도록 주의가 필요하다.
- * 을 사용하여 import 해도 모듈 내 private 변수들은 명시적으로 export 하지 않는 한 가져올 수 없다.
// math.js
export default function add(x, y) {
return x + y
}
export function multiply(x) {
return x * 2
}
export function subtract(x, y) {
return x - y
}
export function square(x) {
return x * x
}
// index.js
import * as math from './math.js'
math.default(7, 8);
math.multiply(8, 9);
math.subtract(10, 3);
math.square(3);
+) styled-components를 사용하는 프로젝트에서 asterix(*)를 사용해 import한 모든 변수와 함수를 가져오는 패턴을 통해 스타일 컴포넌트와 리액트 컴포넌트를 분리해서 작성을 시도한 적이 있다. 관심 있으신 분들은 보다 자세한 내용을 여기에서 볼 수 있다.
Dynamic import
파일의 맨 위에서 모듈들을 import하면 파일 내 다른 코드들이 실행되기 전에 해당 모듈이 로드된다.
사족:
로드와 실행은 다른 것 같다. import 하는 것만으로는 모듈이 실행되지 않는다. 하지만 import한 변수나 함수를 모듈 내에서 사용하면 해당 모듈보다 import한 모듈이 먼저 실행된다.
// math.js console.log("math.js 실행"); // (1) export function add(x, y) { return x + y; } export function multiply(x) { return x * 2; } export function subtract(x, y) { return x - y; } export function square(x) { return x * x; } // App.jsx import { multiply } from "./math.js"; function App() { console.log("app 컴포"); // (2) console.log(multiply(2, 6)); // (3) return <></>; } export default App;
위의 코드에서 브라우저 콘솔을 보면 (1), (2), (3)의 순서로 찍힌다.
반면, (3) 라인을 지우고, App.jsx 파일의 제일 상단에 있는 import 구문만 남긴 경우 콘솔에는 (2)만 찍힌다. 즉, math.js를 import 했어도 import 해온 함수를 사용하지 않기 때문에 (1)이 콘솔에 찍히지 않는다.
다시 본론으로 돌아와서 어떤 상황에서는 특정 조건에서만 특정 모듈을 로드해야 할 때가 있는데, dynamic import를 사용하면 필요에 따라 모듈을 import 할 수 있다.
import("module").then((module) => {
module.default();
module.namedExport();
});
// Or with async/await
(async () => {
const module = await import("module");
module.default();
module.namedExport();
})();
form을 통해 양식을 제출한 경우에만 moduleA를 가져오는 코드 예제를 살펴보자.
then 절에서 필요한 코드만 가져온다. 가져온 코드에 대한 모든 호출은 해당 함수 내부에 있어야 한다. 이러한 방식을 사용하면 번들링 시 분할된 코드(청크)를 지연 로딩시키거나 요청 시에만 로딩할 수 있다.
form.addEventListner("submit", e => {
e.preventDefault();
import('library.moduleA')
.then(module => module.default)
.then(someFunction())
.catch(handleError());
});
const someFunction = () => {
// moduleA를 여기에서 사용
}
아래 예제에서는 사용자가 버튼을 클릭했을 때 모듈을 불러와 기능을 사용하고 있다. (codesandbox playground)
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
<link rel="stylesheet" href="./src/styles.css" />
</head>
<body>
<div id="app"></div>
<button id="btn">Load math module</button>
<script src="index.js"></script>
</body>
</html>
// index.js
const button = document.getElementById("btn");
button.addEventListener("click", () => {
import("./math.js").then((module) => {
console.log("Add: ", module.add(1, 2));
console.log("Multiply: ", module.multiply(3, 2));
button.innerHTML = "Check the console";
});
});
/*************************** */
/**** Or with async/await ****/
/*************************** */
// button.addEventListener("click", async () => {
// const module = await import("./math.js");
// console.log("Add: ", module.add(1, 2));
// console.log("Multiply: ", module.multiply(3, 2));
// });
모듈을 동적으로 로딩하여 페이지 로딩 타임을 줄일 수 있다. 기능이 필요할 때에만 로드하고, 파싱하고, 컴파일하여 코드를 사용하게 되는 것이다.
필요할 때 모듈을 로딩하는 것 외에도 import() 함수는 인자로 표현식을 받는다. 템플릿 리터럴도 사용할 수 있기 때문에 필요에 따라 변수로 필요한 모듈을 받아오도록 할 수 있다.
이 예제(codesandbox playground)에서 date.js 모듈은 사용자가 버튼을 클릭할 때 동적으로 moment 모듈을 불러온다. 사용자가 날짜 확인을 하지 않아도 되면 서드파티 모듈 자체를 다운로드하지 않도록 할 수 있는 것이다.
또한 목록의 각 버튼을 클릭했을 때 로컬에 있는 png 파일을 동적으로 불러오려면 아래처럼 순서를 템플릿 스트링으로 넘기는 것도 가능하다.
const res = await import(`../assets/dog${num}.png`);
즉, dynamic import를 사용하면 사용자의 입력이나 어떤 데이터의 결과에 따라 유연하게 모듈을 로드하여 사용할 수 있다.
dynamic import는 React.lazy와 함께 사용할 수 있다고 하는데, 아직 이 부분은 잘 모르겠다. 다음 기회에 공부를 해보자.
📚 더 읽어보면 좋은 포스팅
'배워서 남 주자' 카테고리의 다른 글
디자인 패턴 - Mediator/Middleware 패턴 (0) | 2024.01.08 |
---|---|
디자인 패턴 - Observer 패턴 (0) | 2023.12.24 |
[TypeScript] VS Code에서 interface 형태를 바로 확인하는 방법 (0) | 2023.11.25 |
디자인 패턴 - Presentational/Container 패턴 (0) | 2023.11.22 |
디자인 패턴 - Compound 패턴 (0) | 2023.11.05 |