Module 패턴
코드를 재사용 가능하면서도 작게 나눈다
📜 원문: patterns.dev - module pattern
📜 번역: https://patterns-dev-kr.github.io/design-patterns/module-pattern/
코드베이스가 커져갈 수록 코드들을 유지보수하기 좋게 쪼개는 것이 중요해집니다. 모듈 패턴이 이 때 코드들을 재사용 가능하면서도 작게 나눌 수 있게 해 줍니다.
또 모듈을 코드를 나누는 과정에 특정 변수들을 파일 내에 private 하게 할 수 있는데 모듈 스코프 내에 변수를 선언하고 명시적으로 외부에 export 하지 않으면 바깥에서 해당 변수에 접근할 수 없습니다. 이를 통해 전역 스코프의 변수들과 이름이 충돌하는 문제를 줄일 수 있죠.
ES2015 모듈
ES2015에는 자바스크립트의 빌트인 모듈 기능이 추가되었습니다. 모듈은 자바스크립트 코드를 포함한 파일이며 일반적인 스크립트와 동작이 약간 다릅니다.
아래 예제를 확인해 보죠. math.js 모듈은 몇가지 계산 함수를 가지고 있죠.
전달된 인자를 더하고, 곱하고, 빼고, 제곱하는 함수를 구현한 math.js 가 있습니다.
이 함수들을 math.js 에서 사용하지 않고 index.js 에서 사용하고 싶습니다. 함수들을 index.js 에서 직접 사용하려고 하면
당연하게도 함수가 존재하지 않는다는 예외가 발생합니다.
함수들을 math.js 외에 다른 파일에서도 사용할 수 있게 하려면 먼저 각 함수들을 export 해야 합니다. 그러기 위해서
export 키워드를 사용합니다. 이는 named export 라 하며 파일 외부에서 사용하기 원하는 것 앞에 export 키워드를 붙이면
됩니다. 예제에서는 각 계산 함수의 앞에 붙여준다. index.js 는 이제 이 함수들을 사용할 수 있게 되었습니다.
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
}
이렇게 add, multiply, subtract, square 함수를 export할 수 있게 만들었습니다. 하지만 외부에서 사용할 수 있도록 export 하는 것 만으로 문제가 해결되지는 않습니다. export된 값들을 사용하기 위해서는 쓰는 쪽에서 명시적으로 import해 주어야
하죠.
index.js 파일의 맨 위에서 import 구문을 사용하여 함수들을 import해야 합니다. 어떤 모듈로부터 기능들을 import하는
지 알리기 위해 from 키워드 뒤에 해당 모듈의 상대 경로를 입력합니다.
import { add, multiply, subtract, square } from './math.js'
이렇게 index.js 에서 math.js 모듈의 함수들을 import하여 사용할 수 있게 되었습니다. 아래 예제를 통해 동작을 확인해 보죠.
이전에 발생했던 참조 예외는 더 이상 발생하지 않고. export된 값들을 사용할 수 있게 되었습니다.
모듈의 장점은 명시적으로 export한 값들만 외부에 노출된다는 것입니다. 명시적으로 export하지 않으면 모듈 내에서만
사용할 수 있습니다.
아래는 math.js 모듈 내에서만 사용할 수 있는 변수 privateValue 를 사용하는 예제입니다.
const privateValue = 'This is a value private to the module!'
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
}
코드에서 보이는 것 처럼 privateValue에는 export키워드를 사용하지 않았습니다.
따라서 math.js 외부에서는 해당 변수에 접근할 수 없습니다.
import { add, multiply, subtract, square } from './math.js'
console.log(privateValue)
/* Error: privateValue is not defined */
모듈 내에 private 변수를 둚으로써 전역 스코프에 의도치 않게 변수를 추가하는 불상사가 줄어듭니다.
개발자는 전역 변수를 덮어쓰게 되거나 하는 등의 걱정 없이 코드를 작성할 수 있습니다.
또 변수명이 충돌되는 것도 막을 수 있죠.
가끔 export된 변수의 이름이 모듈 내 로컬 변수와 이름이 겹칠 수 있습니다.
import { add, multiply, subtract, square } from './math.js'
function add(...args) {
return args.reduce((acc, cur) => cur + acc)
} /* Error: add has already been declared */
function multiply(...args) {
return args.reduce((acc, cur) => cur * acc)
}
/* Error: multiply has already been declared */
위의 경우 모듈 스코프에 add, multiply 함수가 존재하고 있는데 동일한 이름의 값을 import하려 할 때 동일한 이름이 이미
존재한다는 에러가 발생합니다. 이 경우 as 키워드를 통해 import한 값의 이름을 변경할 수 있습니다.
아래 예제에서는 add, multiply 를 각각 addValues, multiplyValues로 변경하여 import하고 있습니다.
import {
add as addValues,
multiply as multiplyValues,
subtract,
square,
} from './math.js'
function add(...args) {
return args.reduce((acc, cur) => cur + acc)
}
function multiply(...args) {
return args.reduce((acc, cur) => cur * acc)
}
/* From math.js module */
addValues(7, 8)
multiplyValues(8, 9)
subtract(10, 3)
square(3)
/* From index.js file */
add(8, 9, 2, 10)
multiply(8, 9, 2, 10)
또 export 키워드를 사용한 선언들 중에 하나를 default export 할 수 있습니다.
아래는add함수를 default export하는 예제입니다. default export를 원하는 값 앞에 export default 키워드를 사용하면 됩니다.
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
}
일반 export와 default export의 차이점은 값을 가져다 쓰는 방법에 있습니다.
이전에 named export를 사용할 땐 대괄호를 사용하였지만 import { module } from 'module' . 반대로 default export된 값
을 import할 땐 대괄호 없이 import하면 됩니다. import module from 'module'
import add, { multiply, subtract, square } from './math.js'
add(7, 8)
multiply(8, 9)
subtract(10, 3)
square(3)
값이 대괄호 없이 import되었다면 해당 값은 default export된 것입니다.
default export된 값을 사용할 때에는 이름을 자유롭게 변경할 수 있습니다. 위의 예제는 add 함수를 가져오기 위
해 add 란 이름을 쓰고 있는데 아래처럼 이름을 바꿀 수 있습니다.
import addValues, { multiply, subtract, square } from './math.js'
addValues(7, 8)
multiply(8, 9)
subtract(10, 3)
square(3)
default export 된 함수의 이름은 add로 명시되어 있지만 자바스크립트가 이를 처리해주기 때문에 이렇게 import하여 호출할 수 있습니다.
또 * 와 이름을 사용하는 하는것으로 모듈 내 default export롤 포함하여 export하는 모든 것들을 한번에 import할 수 있습니다.
export된 모든 것을 포함하는 객체 형태로 사용할 수 있는데 아래는 math.js 의 모든 export들을 math 라는 이름으로 가져오는 예제입니다.
import * as math from './math.js'
math 객체에 math.js 모듈이 export하는 것들이 객체 형태로 담겨 있습니다.
import * as math from './math.js'
math.default(7, 8)
math.multiply(8, 9)
math.subtract(10, 3)
math.square(3)
이 경우 해당 모듈이 export하는 모든 것을 가져오기 때문에 불필요한 것들이 딸려오지 않도록 주의가 필요합니다.
* 을 사용하여 import하여도 모듈 내 private 변수들은 명시적으로 export하지 않는 한 가져올 수 없습니다.
React
React앱을 개발할 때 앱의 규모가 커 지면 많은 컴포넌트를 다루게 됩니다.
컴포넌트들을 한 파일에 모두 선언하지 않고 각 모듈에 하나의 컴포넌트를 선언하게 됩니다.
아래는 목록 컴포넌트 list listItem, 그리고 할 일 입력을 위한 input field를 가진 예제입니다.
예제에서 컴포넌트들은 각각의 파일로 분리되어 있습니다.
- TodoList.js 에는 List 컴포넌트가 있다
- Button.js 에는 커스텀 된 Button 컴포넌트가 있다
- Input.js 에는 커스텀 된 Input 컴포넌트가 위치한다
앱 전체에서 material-ui가 제공하는 Button과 Input을 직접 사용하지 않고 각각 파일에서 커스텀하여 사용하고 있습니다.
각 파일 안에는 style객체를 이용해 버튼과 텍스트박스의 스타일을 정의하고 있죠.
사용하는 쪽에서는 이 모듈만 사용하면 직접 처리할 필요 없이 디자인된 컴포넌트를 사용할 수 있는 것입니다.
각 파일에는 스타일 지정을 위해 모듈 스코프 변수를 선언하고 있지만 모듈 구문으로 인해 이름 충돌은 일어나지 않으므
로 style 이라는 일반적인 이름을 사용하고 있습니다.
Dynamic import
파일의 맨 위에서 모듈들을 import하면 파일 내 다른 코드들이 실행되기 전에 해당 모듈이 로드됩니다.
어떤 상황에서는 특정 조건에서만 특정 모듈을 로드해야 할 때가 있는데. Dynmic import를 사용하면 필요할 때만 로드할 수 있습니
다.
import('module').then(module => {
module.default()
module.namedExport()
})
// Or with async/await
;(async () => {
const module = await import('module')
module.default()
module.namedExport()
})()
모듈을 동적으로 로딩하여 페이지 로딩 타임을 줄일 수 있습니다. 기능이 필요할 때에만 로드하고 파싱하고 컴파일하여 코드를 사용하게 되는것이죠.
또 필요할 때 모듈을 로딩하는것 외에도 import() 함수는 인자로 표현식을 받습니다.
템플릿 리터럴도 사용 가능하기 때문에 필요에 따라 변수로 필요 모듈을 받아오도록 할 수도 있죠.
위의 예제에서 date.js 모듈은 사용자가 버튼을 클릭할 때 동적으로 moment 모듈을 불러옵니다.
사용자가 날짜를 확인하지 않아도 되면 서드파티 모듈 자체를 다운로드하지 않도록 할 수 있는 것이죠.
목록의 각 버튼을 클릭했을 때 로컬에 있는 png파일을 동적으로 불러오려면 아래처럼 순서를 템플릿 스트링으로 넘기는것
도 가능합니다.
const res = await import(`../assets/dog${num}.png`)
사용자의 입력이나 어떤 데이터의 결과에 따라 유연하게 모듈을 로드하여 사용할 수 있습니다.
모듈 패턴을 사용하면 코드의 일부분을 캡슐화 할 수 있습니다. 이는 의도치 않은 전역 변수 할당을 예방할 수 있어 여러 의존 모듈을 사용하거나 네임스페이스를 사용할 때 안전합니다.
모든 자바스크립트 런타임에서 ES2015의 모듈을 사용하려면 바벨과 같은 트랜스파일러가 필요합니다.
'프로그래밍(Basic) > 디자인 패턴(JS)' 카테고리의 다른 글
[바미] Mediator/Middleware 패턴 (0) | 2022.09.23 |
---|---|
[바미] Mixin 패턴 (0) | 2022.09.22 |
[바미] Observer 패턴 (1) | 2022.09.19 |
[바미] Container/Presentational 패턴 (0) | 2022.09.15 |
[바미] Prototype 패턴 (0) | 2022.09.14 |