JavaScript
Import / Export
ES export ขั้นสูง — default, re-export, barrel file
เรียนรู้ default export, re-export ด้วย export { } from และ barrel file เพื่อจัดการ module อย่างยืดหยุ่น
ทบทวน — export ที่เรียนมาแล้ว
ในบท **export-module** เราเรียนวิธี export แบบพื้นฐาน: - `export { name1, name2 };` — export แยกหลังประกาศ - `export function`, `export const` — export ตอนประกาศ (inline) ในบท **import-advanced** เราเห็นตัวอย่าง `export default` มาบ้างแล้ว — รู้ว่า default export คือการ export ค่าหลักเพียงค่าเดียวของ module (module หนึ่งมี default export ได้แค่ 1 ค่า) บทนี้เราจะเรียนฝั่ง export อย่างเต็มรูปแบบ: - **default export** — วิธีเขียนและหลักการเลือกใช้ - **re-export** — ส่งต่อ export จาก module อื่นโดยไม่ต้อง import ก่อน - **barrel file** — รวม module ย่อย ๆ ให้ import จากที่เดียว
// math.js — named export แบบแยกหลังประกาศ
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
const PI = 3.14159;
export { add, multiply, PI };
// หรือเขียนแบบ inline ก็ได้
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;- **Named export** — `export { }` หรือ `export function/const` — 1 module export ได้หลายตัว
- **Default export** — `export default` — 1 module export ได้แค่ 1 ตัว — เรียนในหัวข้อถัดไป
- **Re-export** — `export { } from` — ส่งต่อ export จาก module อื่น — เรียนในบทนี้
- **Barrel file** — `index.js` ที่รวม re-export หลาย module — เรียนในบทนี้
default export — export default
Default export คือการ export ค่าที่เป็น "ตัวหลัก" ของ module — module หนึ่งมี default export ได้แค่ 1 ค่า Syntax: `export default ค่าที่ต้องการ;` หรือ: `export default function name() { ... }` หรือ: `export default class Name { ... }`
// วิธีที่ 1 — export default ค่าโดยตรง
// greeting.js
export default "สวัสดีครับ";
// วิธีที่ 2 — export default function
// calculator.js
export default function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// วิธีที่ 3 — export default ตัวแปรที่ประกาศไว้ก่อน
// config.js
const API_URL = "https://api.example.com";
export default API_URL;
// วิธีที่ 4 — export default class
// User.js
export default class User {
constructor(name) {
this.name = name;
}
greet() {
return "สวัสดี " + this.name;
}
}**Default export vs Named export — เมื่อไหร่ใช้แบบไหน?** - **Default export** เมื่อ module มีฟังก์ชันหรือค่าเดียวที่เป็นพระเอก — เช่น module `calculator.js` ที่มีฟังก์ชันหลักแค่ `calculateTotal` - **Named export** เมื่อ module มีหลายฟังก์ชันหรือค่าที่เป็นประโยชน์ — เช่น module `math.js` ที่มี `add`, `subtract`, `multiply`, `PI` - **ใช้ทั้งคู่** ได้ — module หนึ่งมี default 1 ตัว + named ได้อีกหลายตัว ```javascript // utils.js — ใช้ทั้ง default และ named export default function formatDate(date) { return date.toLocaleDateString('th-TH'); } export function toUpper(str) { return str.toUpperCase(); } export const VERSION = '1.0.0'; ``` ข้อสำคัญ: - module หนึ่งมี `export default` ได้ **แค่ 1 ตัว** — เขียนซ้ำจะ error - `export default` ห้ามใช้กับ `const`, `let`, `var` โดยตรง — ต้องประกาศแยกก่อน เช่น `const PI = 3.14; export default PI;` - ไฟล์ที่ import default จะตั้งชื่ออะไรก็ได้ — (การ import เรียนไปแล้วในบท import-advanced)
- **`export default`** — export ค่าหลักของ module — 1 module มีได้แค่ 1 ตัว
- **Default export** ใช้ `export default value;`, `export default function() {}`, หรือ `export default class {}`
- **Named export** ใช้ `export { }` หรือ `export function/const` — 1 module มีได้หลายตัว
- **ใช้ทั้งคู่พร้อมกันได้** — `export default` 1 ตัว + `export { }` กี่ตัวก็ได้
- **ห้ามเขียน `export default const/let/var` โดยตรง** — ต้องประกาศแยกก่อน → `export default name;`
re-export — export { } from './file.js'
Re-export คือการ "ส่งต่อ" export จาก module อื่น โดยไม่ต้อง import เข้ามาก่อน **ประโยชน์:** - รวม export หลาย module มาไว้ที่ไฟล์เดียว — ผู้ใช้ import จากที่เดียวก็พอ - เปลี่ยนชื่อ export ตอนส่งต่อ ได้ด้วย `as` Syntax: `export { name1, name2 } from './other-module.js';`
// math.js — module ต้นทาง
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export const PI = 3.14159;
// math-reexport.js — re-export จาก math.js
export { add, multiply, PI } from './math.js';
// main.js — import จากไฟล์ที่ re-export
import { add, multiply, PI } from './math-reexport.js';
console.log(add(5, 3)); // 8
console.log(multiply(4, 2)); // 8
console.log(PI); // 3.14159**Re-export พร้อมเปลี่ยนชื่อ:** ```javascript // math.js export function add(a, b) { return a + b; } // re-export พร้อมเปลี่ยนชื่อ export { add as sum } from './math.js'; // ไฟล์อื่นใช้ชื่อใหม่ได้: import { sum } from './re-export.js'; console.log(sum(5, 3)); // 8 ```
- **Re-export** — `export { name } from './file.js'` — ส่งต่อ export โดยไม่ต้อง import ก่อน
- **เปลี่ยนชื่อได้** — `export { old as new } from './file.js'`
- **เลือกเฉพาะบางตัวได้** — ไม่ต้อง re-export ทุกอย่างจาก module ต้นทาง
- **ใช้กับ named export เท่านั้น** — default export ต้องใช้วิธีอื่น (เรียนในหัวข้อถัดไป)
re-export ทั้งหมด — export * from './file.js'
`export * from './file.js'` คือการ re-export **ทุก named export** จากอีก module ในบรรทัดเดียว **ข้อควรรู้:** - `export *` จะ re-export เฉพาะ **named export** — ไม่รวม default export - ถ้ามีชื่อซ้ำกันระหว่าง module ที่ re-export — ตัวที่มาก่อนจะถูก override Syntax: `export * from './module.js';`
// math.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export function multiply(a, b) {
return a * b;
}
export const PI = 3.14159;
// math-all.js — re-export ทุกอย่างจาก math.js
export * from './math.js';
// main.js — import จากไฟล์ที่ re-export ทุกอย่าง
import { add, subtract, multiply, PI } from './math-all.js';
console.log(add(5, 3)); // 8
console.log(subtract(5, 3)); // 2
console.log(multiply(4, 2)); // 8
console.log(PI); // 3.14159// utils/string.js
export function toUpperCase(text) {
return text.toUpperCase();
}
export function toLowerCase(text) {
return text.toLowerCase();
}
// utils/number.js
export function toFixed(num, decimals) {
return num.toFixed(decimals);
}
export const PI = 3.14159;
// utils/index.js — re-export ทุกอย่างจากทั้ง 2 module
export * from './string.js';
export * from './number.js';
// main.js
import { toUpperCase, toLowerCase, toFixed, PI } from './utils/index.js';
console.log(toUpperCase("hello")); // "HELLO"
console.log(toFixed(PI, 2)); // "3.14"- **`export * from`** — re-export ทุก named export จากอีก module
- **ไม่รวม default export** — `export *` ไม่ re-export default
- **ใช้กับหลาย module ได้** — วาง `export * from` หลายบรรทัดเพื่อรวม export
- **ชื่อซ้ำจะถูก override** — ถ้า 2 module export ชื่อเดียวกัน ตัวสุดท้ายจะชนะ
namespace re-export — export * as name from './file.js'
`export * as name from './file.js'` เป็นการ re-export ทุกอย่างจากอีก module แต่**จัดกลุ่มเข้า namespace** — คล้ายกับ `import * as` แต่ใช้ในฝั่ง export Syntax: `export * as ชื่อNamespace from './module.js';` **ประโยชน์:** - รวม module หลายตัวใน barrel file โดยที่ชื่อไม่ชนกัน — เพราะแต่ละ module ถูกห่อด้วย namespace - ผู้ใช้เลือก import เฉพาะ namespace ที่ต้องการ
// math.js
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export const PI = 3.14159;
// string-utils.js
export function toUpper(str) {
return str.toUpperCase();
}
export function toLower(str) {
return str.toLowerCase();
}
// utils/index.js — barrel file ใช้ export * as
export * as math from './math.js';
export * as str from './string-utils.js';
// main.js — import namespace จาก barrel
import { math, str } from './utils/index.js';
console.log(math.add(5, 3)); // 8
console.log(math.PI); // 3.14159
console.log(str.toUpper("hello")); // "HELLO"
console.log(str.toLower("WORLD")); // "world"**`export *` ธรรมดา vs `export * as`:** - `export * from './math.js'` — re-export ทุกชื่อแบบ flat — `add`, `multiply`, `PI` ปรากฏเป็นชื่อ standalone - `export * as math from './math.js'` — re-export ทุกชื่อภายใต้ namespace `math` — ต้องใช้ `math.add`, `math.PI` ใช้ `export * as` เมื่อ: - หลาย module มีชื่อ export ซ้ำกัน — ต้องการหลีกเลี่ยงการ override - ต้องการให้ import ได้เป็นกลุ่ม — ผู้ใช้รู้ว่า function นี้มาจาก module ไหน
- **`export * as ns from`** — re-export ทุกอย่างจากอีก module เป็น namespace
- **ไม่ชนกัน** — แต่ละ module อยู่ใน namespace ของตัวเอง
- **Import ด้วย `import { ns } from`** — ไม่ใช่ `import * as`
- **ใช้คู่กับ `export * from` ได้** — ใน barrel file เดียวกัน
barrel file — รวม module ด้วย index.js
Barrel file คือไฟล์ `index.js` (หรือชื่ออื่นก็ได้) ที่รวม re-export จาก module ย่อยหลายตัว — เพื่อให้ผู้ใช้ import จากที่เดียวแทนที่จะต้อง import จากแต่ละไฟล์แยกกัน **โครงสร้างปกติ:** ``` utils/ ├── string.js ├── number.js ├── date.js └── index.js ← barrel file ``` แทนที่จะเขียน: ```javascript import { toUpperCase } from './utils/string.js'; import { toFixed } from './utils/number.js'; import { formatDate } from './utils/date.js'; ``` ใช้ barrel file เขียนแบบนี้แทน: ```javascript import { toUpperCase, toFixed, formatDate } from './utils/index.js'; ```
// utils/string.js
export function toUpperCase(text) {
return text.toUpperCase();
}
export function toLowerCase(text) {
return text.toLowerCase();
}
// utils/number.js
export function toFixed(num, decimals) {
return num.toFixed(decimals);
}
export function toPrecision(num, precision) {
return num.toPrecision(precision);
}
export const PI = 3.14159;
export const E = 2.71828;
// utils/date.js
export function formatDate(date) {
return date.toLocaleDateString('th-TH');
}
export function formatTime(date) {
return date.toLocaleTimeString('th-TH');
}
// utils/index.js — barrel file
export * from './string.js';
export * from './number.js';
export * from './date.js';
// main.js — import ทุกอย่างจาก barrel
import { toUpperCase, toLowerCase } from './utils/index.js';
import { toFixed, toPrecision, PI, E } from './utils/index.js';
import { formatDate, formatTime } from './utils/index.js';
console.log(toUpperCase("hello world")); // "HELLO WORLD"
console.log(toFixed(PI, 4)); // "3.1416"
const now = new Date();
console.log(formatDate(now)); // "10/5/2569"- **Barrel file** — ไฟล์ที่รวม re-export หลาย module — มักใช้ชื่อ `index.js`
- **วิธีสร้าง** — สร้าง `index.js` ใช้ `export * from` จากทุกไฟล์ย่อย
- **ประโยชน์** — import จากที่เดียว แทนที่จะต้องรู้ว่าแต่ละฟังก์ชันอยู่ในไฟล์ไหน
- **ใช้ในโปรเจกต์จริง** — ทุกครั้งที่สร้างโฟลเดอร์ที่มีหลาย module — เพิ่ม `index.js` barrel file
- **ใช้ `export * as` เพิ่มได้** — ถ้าต้องการจัดกลุ่มเป็น namespace
ข้อควรระวังและข้อผิดพลาดที่พบบ่อย
มือใหม่ที่เริ่มใช้ export ขั้นสูงมักเจอ error จากการใช้ `export default` ซ้ำ หรือ re-export ผิดรูปแบบ — มาดูเรื่องที่พบบ่อยและวิธีแก้:
| เรื่องที่เข้าใจผิด / ทำผิด | สิ่งที่เกิดขึ้นจริง | วิธีแก้ |
|---|---|---|
| `export default` มากกว่า 1 ตัวใน module เดียว | `SyntaxError: Duplicate export of 'default'` | 1 module มี default export ได้แค่ 1 ตัว — ลบตัวที่ซ้ำออก หรือเปลี่ยนเป็น named export |
| เขียน `export default const PI = 3.14;` | `SyntaxError: Unexpected token 'const'` | ประกาศแยกก่อน: `const PI = 3.14; export default PI;` |
| `export * from` re-export default ไม่ได้ | default export ของ module ต้นทางจะไม่ถูก re-export | ต้อง re-export default แยก: `export { default } from './file.js'` |
| ชื่อ export ซ้ำกันจากหลาย module ใน barrel file | ไม่มี error — แต่ export ของ module แรกจะถูก override | ใช้ `export * as ns from` แทน หรือตั้งชื่อไม่ให้ซ้ำกัน |
| ลืม re-export บาง module ใน barrel file | `SyntaxError: does not provide an export named '...'` | ตรวจสอบว่า barrel file มี `export * from` ครบทุก module ที่ต้องการ |
// ❌ export default ซ้ำ
export default function add(a, b) {
return a + b;
}
export default function subtract(a, b) {
return a - b;
}
// ❌ SyntaxError: Duplicate export of 'default'
// ✅ ถูกต้อง — ใช้ default + named
export default function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// ========================================
// ❌ export default const
export default const PI = 3.14;
// ❌ SyntaxError: Unexpected token 'const'
// ✅ ถูกต้อง — ประกาศแยกก่อน
const PI = 3.14;
export default PI;
// ========================================
// ❌ barrel file ลืม export บาง module
// utils/index.js — ลืม export date.js
export * from './string.js';
export * from './number.js';
// main.js
import { formatDate } from './utils/index.js';
// ❌ SyntaxError: does not provide an export named 'formatDate'
// ✅ ถูกต้อง — เพิ่ม export * from './date.js'สรุป — export ขั้นสูง
- **Default export** — `export default value;` — module หนึ่งมีได้แค่ 1 ตัว ใช้เมื่อ module มีค่าหลักเดียว
- **Named export** — `export { name }` หรือ `export function/const` — module หนึ่งมีได้หลายตัว
- **Re-export** — `export { name } from './file.js'` — ส่งต่อ named export จากอีก module
- **Re-export ทั้งหมด** — `export * from './file.js'` — ส่งต่อทุก named export (ไม่รวม default)
- **Namespace re-export** — `export * as ns from './file.js'` — ส่งต่อแบบจัดกลุ่ม หลีกเลี่ยงชื่อชน
- **Barrel file** — `index.js` ที่ใช้ `export * from` รวม module ย่อย — import จากที่เดียว
- **Default + Named** — ใช้ร่วมกันได้ — default 1 ตัว + named กี่ตัวก็ได้
| สิ่งที่อยากรู้ | คำตอบสั้น |
|---|---|
| default export syntax | `export default value;` หรือ `export default function() {}` |
| re-export syntax | `export { name1, name2 } from './file.js'` |
| re-export ทั้งหมด | `export * from './file.js'` |
| namespace re-export | `export * as ns from './file.js'` |
| barrel file | `index.js` ที่ใช้ `export * from` รวม module ย่อย ๆ |
| default ซ้ำ | ห้าม — 1 module มี default ได้แค่ 1 ตัว |
ลองทำ — สร้าง barrel file และ re-export
ถึงเวลาลองใช้ re-export และ barrel file ด้วยตัวเองแล้ว! เราจะสร้าง project เล็ก ๆ ที่มี **barrel file** รวม module ย่อย — ประกอบด้วย **6 ไฟล์**: 1. **`utils/string.js`** — module จัดรูปแบบ string 2. **`utils/number.js`** — module จัดรูปแบบ number 3. **`utils/date.js`** — module จัดรูปแบบ date 4. **`utils/index.js`** — barrel file re-export ทุกอย่างจาก 3 module ข้างต้น 5. **`main.js`** — ไฟล์หลักที่ import จาก barrel file 6. **`index.html`** — หน้าเว็บที่โหลด `main.js` เปิด editor ของคุณ สร้าง folder ใหม่ (ตั้งชื่ออะไรก็ได้ เช่น `advanced-export`) แล้วสร้างไฟล์ตามด้านล่าง
// utils/string.js
export function toUpperCase(text) {
return text.toUpperCase();
}
export function toLowerCase(text) {
return text.toLowerCase();
}
export function capitalize(text) {
return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
}// utils/number.js
export function toFixed(num, decimals) {
return num.toFixed(decimals);
}
export function toPrecision(num, precision) {
return num.toPrecision(precision);
}
export const PI = 3.14159;
export const E = 2.71828;// utils/date.js
export function formatDate(date) {
return date.toLocaleDateString('th-TH');
}
export function formatTime(date) {
return date.toLocaleTimeString('th-TH');
}// utils/index.js — barrel file
// re-export ทุก named export จาก 3 module ย่อย
export * from './string.js';
export * from './number.js';
export * from './date.js';// main.js — import จาก barrel file ไฟล์เดียว
import { toUpperCase, toLowerCase, capitalize } from './utils/index.js';
import { toFixed, toPrecision, PI, E } from './utils/index.js';
import { formatDate, formatTime } from './utils/index.js';
console.log("=== String Helpers ===");
console.log(toUpperCase("hello world"));
console.log(toLowerCase("HELLO WORLD"));
console.log(capitalize("hello world"));
console.log("\n=== Number Helpers ===");
console.log("PI =", PI);
console.log("E =", E);
console.log("PI.toFixed(4) =", toFixed(PI, 4));
console.log("E.toPrecision(4) =", toPrecision(E, 4));
console.log("\n=== Date Helpers ===");
const now = new Date();
console.log("วันนี้:", formatDate(now));
console.log("เวลา:", formatTime(now));<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<title>Advanced Export — Barrel File</title>
</head>
<body>
<h1>เปิด Console (F12) เพื่อดูผลลัพธ์</h1>
<script type="module" src="main.js"></script>
</body>
</html>โครงสร้างไฟล์หลังจากสร้างครบ: ``` advanced-export/ ├── utils/ │ ├── string.js │ ├── number.js │ ├── date.js │ └── index.js ← barrel file ├── main.js └── index.html ``` **วิธีรัน**: คลิกขวาที่ `index.html` ใน VS Code → เลือก **"Open with Live Server"** เบราว์เซอร์จะเปิดแท็บใหม่ จากนั้นกด **F12** เพื่อเปิด Console ⚠️ ห้ามเปิดไฟล์ HTML ตรง ๆ จาก file explorer — ต้องรันผ่าน **Live Server** เท่านั้น (URL ต้องเป็น `http://127.0.0.1:...`)
=== String Helpers ===
HELLO WORLD
hello world
Hello world
=== Number Helpers ===
PI = 3.14159
E = 2.71828
PI.toFixed(4) = 3.1416
E.toPrecision(4) = 2.718
=== Date Helpers ===
วันนี้: 10/5/2569
เวลา: (เวลาปัจจุบัน)- ถ้า console **ไม่แสดงอะไรเลย** → เช็คว่า `index.html` มี `type="module"` ใน `<script>` หรือยัง
- ถ้า console **แดง error** → อ่านข้อความ error ดู — ส่วนใหญ่เกิดจาก path ผิด (ลืม `./` หรือลืม `.js`)
- ถ้า console **แดง CORS error** → เปิดไฟล์ตรง ๆ ไม่ได้ผ่าน Live Server — ต้องใช้ Live Server
- ถ้าเห็นผลลัพธ์ครบตามด้านบน → **ผ่านแล้ว!** barrel file ทำงานถูกต้อง
เห็นผลลัพธ์ครบแล้ว? ลอง **ทดลองเปลี่ยนโค้ด** เพื่อทำความเข้าใจให้ลึกขึ้น: **ทดลองที่ 1 — ใช้ export * as:** แก้ `utils/index.js` จาก `export * from` เป็น `export * as`: ```javascript // utils/index.js export * as str from './string.js'; export * as num from './number.js'; export * as dt from './date.js'; ``` แล้วใน `main.js` เปลี่ยน import เป็น: ```javascript import { str, num, dt } from './utils/index.js'; console.log(str.toUpperCase("hello")); console.log(num.PI); console.log(dt.formatDate(new Date())); ``` refresh ดูผลลัพธ์ — คราวนี้ทุกอย่างอยู่ใน namespace ของตัวเอง **ทดลองที่ 2 — เพิ่ม default export:** แก้ `utils/string.js` เพิ่ม default export: ```javascript export default "String Utils v1.0"; ``` แล้วลอง import default จาก barrel file: ```javascript import stringVersion from './utils/index.js'; console.log(stringVersion); ``` — จะ **error** เพราะ `export * from` ไม่ re-export default! **ทดลองที่ 3 — ลองลบ re-export บางบรรทัดออก:** ใน `utils/index.js` ลบ `export * from './date.js'` — แล้ว refresh ดู error ทำความเข้าใจ error แล้วจำไว้ — การจัด module ให้ถูกต้องเป็นทักษะสำคัญสำหรับโปรเจกต์จริง