Post

모듈 순환 참조(Circular Dependency) 개선하기

모듈 동작 원리

모듈 순환 참조의 문제를 보기에 앞서 브라우저에서 모듈이 어떻게 동작하는지 먼저 알아보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
// index.js
import './a.js'

// a.js
import { sayHello } from './b.js'
export const NAME = 'mike'
sayHello()

// b.js
import { NAME } from './a.js'
export const sayHello = () => {
  console.log('hello~!', NAME)
}

import를 통해 모듈을 가져오면 브라우저는 모듈을 평가한 다음, 이를 실행한다.

모든 모듈은 모듈 객체를 갖고 있으며, 모듈이 내보내는 변수와 함수는 모듈 객체에 추가된다.

위의 코드에서 sayHello는 아래와 같이 표현될 수 있다.

1
2
3
export const sayHello = () => {
  console.log('hello~!', aModuleObject.NAME)
}

동일한 모듈을 여러 곳에서 참조해도 모듈은 최초 호출 시 단 한 번만 실행된다.

1
2
3
4
5
6
7
8
9
10
11
// alert.js
alert("모듈이 평가되었습니다!");

// a.js
import `alert.js`;
// 모듈이 평가되었습니다!
import `b.js`;

// b.js
import `alert.js`;
// 아무 일도 발생하지 않는다.

Circular Dependency (순환 참조)

이제 모듈의 순환 참조가 왜 에러를 발생시키는지 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
// index.js
import './a.js'

// a.js
import { NAME } from './b.js'
export const sayHello = () => {
  console.log('hello~!', NAME)
}

// b.js
import { sayHello } from './a.js'
export const NAME = 'mike'
sayHello()
  1. index.js에서 a 모듈을 평가한다.
  2. a 모듈에서 b 모듈을 평가한다.
  3. b 모듈에서 a 모듈을 참조하고 있지만 위에서 이미 평가했기 때문에 a 모듈을 평가하지 않는다.
  4. b 모듈에서 sayHello를 실행하지만 aModuleObject.sayHello가 존재하지 않으므로 에러가 발생한다.

순환 끊기

최하위 모듈 만들기

a와 b 모듈에서 모두 의존하는 최하위 모듈을 만드는 방법도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// index.js
import './modules.js'

// modules.js
export * from './a.js'
export * from './b.js'

// a.js
import { sayHello } from './modules.js'
export const NAME = 'mike'
sayHello()

// b.js
import { NAME } from './modules.js'
export const sayHello = () => {
  console.log('hello~!', NAME)
}

modules.js 파일을 만들어서 a.js와 b.js를 export 한다.

a와 b 모듈을 사용할 때 modules에서 가져온다.

이렇게 하면 a와 b가 서로 직접 의존하지 않으며, modules에서 NAME과 sayHello를 export하는 것과 같은 결과가 된다.

주의할 점은, modules에서 a와 b를 import할 때 평가 순서를 주의해야 한다.

만약 a보다 b를 먼저 export 한다면 NAME 변수가 정의되기 전이므로 에러가 발생한다.

의존성 역전시키기

예제 코드에서 의존성의 방향은 index.js -> a.js -> b.js 이다. 즉 모듈간의 순위는 index > a > b 이다.

a는 b보다 순위가 높으므로 b를 참조할 수 있지만, b는 a를 참조해선 안 된다.

에러가 나는 원인은 b가 a를 참조했기 때문이다.

b에서 의존하고 있는 sayHello 함수를 a에서 b로 이동시킨다.

1
2
3
4
5
6
7
8
9
10
11
12
// index.js
import './a.js'

// a.js
import { sayHello } from './b.js'
sayHello()

// b.js
export const NAME = 'mike'
export const sayHello = () => {
  console.log('hello~!', NAME)
}

이렇게하면 모듈 평가 순위에 거스르지 않아서 에러가 발생하지 않는다.

참고사이트

JS 모듈 시스템과 순환 참조 문제
모듈 소개
circular-dependency-plugin

This post is licensed under CC BY 4.0 by the author.