웹 사이트가 하는 일이 증가함에 따라 스크립트 파일의 양이 증가하게되었고 예전에는 전역 스코프를 활용하여 스크립트 파일들의 통신을 담당하였지만 스크립트 파일간의 실행순서와 의존도 문제가 심해짐에 따라 모듈이 만들어졌다.
모듈은 스크립트 파일간의 의존성을 쉽게 파악하며 훨씬 간편학 관리할 수 있고 실행순서를 쉽게 제어할 수 있다.
모듈과 컴포넌트는 자주 혼용되며 모듈은 설계 시점에 의미있는 요소를 가리키며, 컴포넌트는 런타임 시점에 의미있는 요소를 가리킨다.
즉, 모듈은 우리가 의식적으로 나눈 요소이고, 컴포넌트는 나눈 요소에 포함되어 실행되어지는 요소를 말합니다.
모듈은 로컬 파일에서 동작하지 않고 HTTP 또는 HTTPS 프로토콜을 통해서만 동작한다.
변수나 함수, 클래스를 선언할 때 맨 앞에 export를 붙이면 내보내기가 가능합니다.
// 배열 내보내기
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// 상수 내보내기
export const MODULES_BECAME_STANDARD_YEAR = 2015;
// 클래스 내보내기
export class User {
constructor(name) {
this.name = name;
}
}
선언부와 export가 떨어져 있어도 내보내기가 가능합니다.
function sayHi(user) {
alert(`Hello, ${user}!`);
}
function sayBye(user) {
alert(`Bye, ${user}!`);
}
export {sayHi, sayBye}; // 두 함수를 내보냄
sayHi와 sayBye를 각각 hi와 bye로 이름을 바꿔 내보내 봅시다.
export {sayHi as hi, sayBye as bye};
// 이제 다른 모듈에서 이 함수들을 가져올 때 이름은 hi와 bye가 됩니다.
import * as say from './파일명.js';
say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!
모듈은 export default
라는 특별한 문법을 지원합니다. export default
를 사용하면 해당 모듈엔 개체가 하나만 있다는 사실을 명확히 나태낼 수 있습니다.
export default class User { // export 옆에 'default'를 추가해보았습니다.
constructor(name) {
this.name = name;
}
}
파일 하나엔 대개 export default가 하나만 있습니다.
이렇게 default를 붙여서 모듈을 내보내면 중괄호 {} 없이 모듈을 가져올 수 있습니다.
import User from './user.js'; // {User}가 아닌 User로 클래스를 가져왔습니다.
new User('John');
가져오고 싶은 함수를 아래와 같이 이에 대한 목록을 만들어 import {...}안에 적어주면 됩니다.
import {sayHi, sayBye} from './파일명.js';
sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!
가져올 것이 많으면 import * as <obj>
처럼 객체 형태로 원하는 것들을 가지고 올 수 있습니다.
import * as say from './say.js';
say.sayHi('John');
say.sayBye('John');
이렇게 한꺼번에 모든 걸 가져오는 방식을 사용하면 코드가 짧아집니다. 그런데도 어떤 걸 가져올 땐 그 대상을 구체적으로 명시하는 게 좋습니다.
as를 사용하면 이름을 바꿔서 모듈을 가져올 수 있습니다.
import {sayHi as hi, sayBye as bye} from './say.js';
hi('John'); // Hello, John!
bye('John'); // Bye, John!
// hello.js
export function hello(name) {
console.log(name);
}
// index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Module Test</title>
</head>
<body>
<script type="module">
import { hello } from './test.js';
hello('Nam');
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Module Test</title>
</head>
<body>
<script type="module">
let a = 5;
let b = 10;
c = a + b; // 엄격 모드이므로 선언안된 c 오류
console.log(c);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Module Test</title>
</head>
<body>
<script type="module">
let a = 5;
let b = 10;
const c = a + b;
</script>
<script type="module">
console.log(c); // module 스크립트는 일단 스크립트와 달리 import 하지 않는한 전역스코프에 등록되지 않으므로 참조불가
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Module Test</title>
</head>
<body>
<script type="module">
import './hello.js';
</script>
<script type="module">
import './hello.js'; // 두 번 호출되었지만 실행은 단 한 번만 한다.
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Module Test</title>
</head>
<body>
<script>
// DOM이 생성되기 전에 실행된다.
console.log('text1');
</script>
<script type="module">
// DOM이 만들어진 후 실행된다.
// defer 옵션을 적용한 효과와 동일
console.log('text2');
</script>
</body>
</html>