Layers (เลเยอร์)
Layers เป็นระดับแรกของลำดับชั้นการจัดระเบียบใน Feature-Sliced Design จุดประสงค์คือเพื่อแยกโค้ดตามระดับความรับผิดชอบที่มันต้องการและจำนวน Modules อื่นๆ ในแอปที่มันพึ่งพา ทุก Layer มีความหมายทาง Semantic พิเศษที่จะช่วยคุณตัดสินใจว่าควรจัดสรรความรับผิดชอบให้โค้ดของคุณมากแค่ไหน
มีทั้งหมด 7 Layers เรียงจากความรับผิดชอบและ Dependency มากที่สุดไปน้อยที่สุด:
- App
- Processes (เลิกใช้แล้ว - Deprecated)
- Pages
- Widgets
- Features
- Entities
- Shared
คุณไม่จำเป็นต้องใช้ทุก Layer ในโปรเจกต์ของคุณ — เพิ่มเฉพาะถ้าคุณคิดว่ามันสร้างคุณค่าให้โปรเจกต์ โดยทั่วไป Frontend projects ส่วนใหญ่จะมีอย่างน้อย Layers Shared, Pages, และ App
ในทางปฏิบัติ Layers คือโฟลเดอร์ที่มีชื่อตัวพิมพ์เล็ก (เช่น 📁 shared, 📁 pages, 📁 app) การเพิ่ม Layers ใหม่ ไม่แนะนำ เพราะความหมาย (Semantics) ของพวกมันถูกกำหนดเป็นมาตรฐานไว้แล้ว
กฎการ Import บน Layers
Layers ประกอบด้วย Slices — กลุ่มของ Modules ที่มีความเกาะเกี่ยว (Cohesive) สูง Dependencies ระหว่าง Slices ถูกควบคุมโดย กฎการ Import บน Layers:
Module (ไฟล์) ใน Slice สามารถ Import Slices อื่นได้เฉพาะเมื่อพวกมันอยู่ใน Layers ที่ต่ำกว่าอย่างเคร่งครัดเท่านั้น
ตัวอย่างเช่น โฟลเดอร์ 📁 ~/features/aaa คือ Slice ชื่อ "aaa" ไฟล์ข้างในนั้น ~/features/aaa/api/request.ts ไม่สามารถ Import โค้ดจากไฟล์ใดๆ ใน 📁 ~/features/bbb ได้ แต่สามารถ Import โค้ดจาก 📁 ~/entities และ 📁 ~/shared ได้ เช่นเดียวกับ Code พี่น้องใดๆ จาก 📁 ~/features/aaa เช่น ~/features/aaa/lib/cache.ts
Layers App และ Shared เป็น ข้อยกเว้น ของกฎนี้ — พวกมันเป็นทั้ง Layer และ Slice ในเวลาเดียวกัน Slices แบ่งโค้ดตาม Business domain และสอง Layers นี้เป็นข้อยกเว้นเพราะ Shared ไม่มี Business domains และ App รวมทุก Business domains เข้าด้วยกัน
ในทางปฏิบัติ หมายความว่า Layers App และ Shared ประกอบขึ้นจาก Segments และ Segments สามารถ Import กันเองได้อย่างอิสระ
คำนิยามของ Layer
ส่วนนี้อธิบายความหมายทาง Semantic ของแต่ละ Layer เพื่อสร้างสัญชาตญาณว่าโค้ดแบบไหนควรอยู่ที่นั่น
Shared
Layer นี้สร้างรากฐาน (Foundation) ให้กับส่วนที่เหลือของแอป เป็นสถานที่สำหรับสร้างการเชื่อมต่อกับโลกภายนอก เช่น Backends, Third-party libraries, Environment และยังเป็นที่สำหรับกำหนด Libraries ของคุณเองที่มีขอบเขตชัดเจน
Layer นี้ เหมือนกับ App layer ไม่มี Slices Slices ตั้งใจให้แบ่ง Layer เป็น Business domains แต่ Business domains ไม่มีอยู่ใน Shared นี่หมายความว่าไฟล์ทั้งหมดใน Shared สามารถอ้างอิงและ Import กันเองได้
นี่คือ Segments ที่คุณมักจะพบใน Layer นี้:
📁 api— API client และอาจรวมถึงฟังก์ชันเพื่อทำ Requests ไปยัง Backend endpoints เฉพาะ📁 ui— Application's UI kit Components ใน Layer นี้ไม่ควรมี Business logic แต่โอเคถ้ามันจะมี Business-theme เช่น คุณสามารถวาง Company logo และ Page layout ที่นี่ Components ที่มี UI logic ก็อนุญาต (เช่น Autocomplete หรือ Search bar)📁 lib— คอลเลกชันของ Internal libraries โฟลเดอร์นี้ไม่ควรถูกปฏิบัติเหมือน Helpers หรือ Utilities (อ่านที่นี่ว่าทำไมโฟลเดอร์เหล่านี้มักกลายเป็นที่ทิ้งขยะ) แต่ Library ทุกอันในโฟลเดอร์นี้ควรมี Area of focus เดียว เช่น Dates, Colors, Text manipulation, ฯลฯ Area of focus นั้นควรถูก Document ในไฟล์ README Developers ในทีมของคุณควรรู้ว่าอะไรเพิ่มได้และเพิ่มไม่ได้ใน Libraries เหล่านี้📁 config— Environment variables, Global feature flags และ Global configuration อื่นๆ สำหรับแอปของคุณ📁 routes— Route constants หรือ Patterns สำหรับ Matching routes📁 i18n— Setup code สำหรับการแปล, Global translation strings
คุณมีอิสระที่จะเพิ่ม Segments มากกว่านี้ แต่ต้องแน่ใจว่าชื่อของ Segments เหล่านี้อธิบายจุดประสงค์ของเนื้อหา ไม่ใช่แก่นแท้ของมัน ตัวอย่างเช่น components, hooks, และ types เป็นชื่อ Segment ที่แย่เพราะมันไม่ได้ช่วยเท่าไหร่เวลาคุณหาโค้ด
Entities
Slices บน Layer นี้เป็นตัวแทนของ Concepts จากโลกความจริงที่โปรเจกต์กำลังทำงานด้วย โดยทั่วไป พวกมันคือคำศัพท์ที่ Business ใช้เพื่ออธิบาย Product ตัวอย่างเช่น Social network อาจทำงานกับ Business entities เช่น User, Post, และ Group
Entity slice อาจประกอบด้วย Data storage (📁 model), Data validation schemas (📁 model), Entity-related API request functions (📁 api), รวมถึง Visual representation ของ Entity นี้ใน Interface (📁 ui) Visual representation ไม่จำเป็นต้องสร้าง UI block ที่สมบูรณ์ — จุดประสงค์หลักคือเพื่อ Reuse รูปลักษณ์เดิมข้ามหลายๆ หน้าในแอป และ Business logic ที่แตกต่างกันอาจถูกแนบเข้าไปผ่าน Props หรือ Slots
ความสัมพันธ์ของ Entity
Entities ใน FSD คือ Slices และโดย Default แล้ว Slices ไม่สามารถรู้เรื่องของกันและกันได้ แต่ในชีวิตจริง Entities มักปฏิสัมพันธ์กัน และบางครั้ง Entity หนึ่งก็เป็นเจ้าของหรือประกอบด้วย Entities อื่น ด้วยเหตุนี้ Business logic ของปฏิสัมพันธ์เหล่านี้จึงควรเก็บไว้ใน Layers ที่สูงกว่า เช่น Features หรือ Pages
เมื่อ Data object ของ Entity หนึ่งประกอบด้วย Data objects อื่นๆ โดยปกติแล้วเป็นความคิดที่ดีที่จะทำให้การเชื่อมต่อระหว่าง Entities ชัดเจนและหลบเลี่ยง Slice isolation โดยสร้าง Cross-reference API ด้วย @x notation เหตุผลคือ Connected entities จำเป็นต้องถูก Refactor พร้อมกัน ดังนั้นดีที่สุดคือทำให้การเชื่อมต่อนั้นไม่มีทางพลาดสายตา
ตัวอย่างเช่น:
import type { Song } from "entities/song/@x/artist";
export interface Artist {
name: string;
songs: Array<Song>;
}
export type { Song } from "../model/song.ts";
เรียนรู้เพิ่มเติมเกี่ยวกับ @x notation ในส่วน Public API for cross-imports
Features
Layer นี้สำหรับ Interactions หลักๆ ในแอปของคุณ สิ่งที่ Users ของคุณสนใจที่จะทำ Interactions เหล่านี้มักเกี่ยวข้องกับ Business entities เพราะนั่นคือสิ่งที่แอปเป็น
หลักการสำคัญสำหรับการใช้ Features layer อย่างมีประสิทธิภาพคือ: ไม่ใช่ทุกอย่างจำเป็นต้องเป็น Feature ตัวบ่งชี้ที่ดีว่าบางอย่างจำเป็นต้องเป็น Feature คือความจริงที่ว่ามันถูก Reuse ในหลายหน้า
ตัวอย่างเช่น ถ้าแอปมีหลาย Editors และพวกมันทั้งหมดมี Comments ดังนั้น Comments คือ Reused feature จำไว้ว่า Slices คือกลไกสำหรับการหาโค้ดอย่างรวดเร็ว และถ้ามี Features เยอะเกินไป อันที่สำคัญจะถูกกลบหายไป
ในอุดมคติ เมื่อคุณมาเริ่มโปรเจกต์ใหม่ คุณควรจะค้นพบฟังก์ชันการทำงานของมันโดยการดูผ่าน Pages และ Features เมื่อตัดสินใจว่าอะไรควรเป็น Feature ให้ Optimize สำหรับประสบการณ์ของคนมาใหม่ในโปรเจกต์เพื่อให้ค้นพบพื้นที่สำคัญใหญ่ๆ ของโค้ดได้อย่างรวดเร็ว
Feature slice อาจประกอบด้วย UI เพื่อทำ Interaction เช่น Form (📁 ui), API calls ที่จำเป็นสำหรับ Action (📁 api), Validation และ Internal state (📁 model), Feature flags (📁 config)
Widgets
Widgets layer ตั้งใจไว้สำหรับ Blocks of UI ขนาดใหญ่ที่พึ่งพาตัวเองได้ (Self-sufficient) Widgets มีประโยชน์ที่สุดเมื่อพวกมันถูก Reuse ข้ามหลาย Pages หรือเมื่อ Page ที่มันอยู่มี Blocks อิสระขนาดใหญ่หลายอัน และนี่คือหนึ่งในนั้น
ถ้า Block of UI ประกอบขึ้นเป็น Content ที่น่าสนใจส่วนใหญ่ของหน้า และไม่เคยถูก Reuse มัน ไม่ควรเป็น Widget และควรวางไว้ข้างในหน้านั้นโดยตรงแทน
ถ้าคุณกำลังใช้ Nested routing system (เช่น Router ของ Remix) มันอาจเป็นประโยชน์ที่จะใช้ Widgets layer ในวิธีเดียวกับที่ Flat routing system ใช้ Pages layer — คือเพื่อสร้าง Router blocks ที่สมบูรณ์ พร้อมด้วย Data fetching, Loading states, และ Error boundaries ที่เกี่ยวข้อง
ในวิธีเดียวกัน คุณสามารถเก็บ Page layouts ไว้บน Layer นี้ได้
Pages
Pages คือสิ่งที่ประกอบขึ้นเป็น Websites และ Applications (หรือที่รู้จักว่า Screens หรือ Activities) หนึ่ง Page มักจะตรงกับหนึ่ง Slice อย่างไรก็ตาม ถ้ามีหลาย Pages ที่คล้ายกันมากๆ พวกมันสามารถถูกรวมกลุ่มเป็น Slice เดียวได้ ตัวอย่างเช่น Registration และ Login forms
ไม่มีลิมิตว่าคุณสามารถวางโค้ดใน Page slice ได้มากแค่ไหน ตราบใดที่ทีมของคุณยังพบว่ามันง่ายที่จะนำทาง ถ้า UI block บนหน้าไม่ได้ถูก Reuse มันถูกต้องสมบูรณ์ที่จะเก็บมันไว้ข้างใน Page slice
ใน Page slice คุณมักจะพบ Page's UI รวมถึง Loading states และ Error boundaries (📁 ui) และ Data fetching และ Mutating requests (📁 api) ไม่ใช่เรื่องปกติที่ Page จะมี Dedicated data model และเศษเสี้ยวของ State เล็กๆ น้อยๆ สามารถเก็บไว้ใน Components เองได้
Processes
Layer นี้ถูกเลิกใช้แล้ว (Deprecated) เวอร์ชันปัจจุบันของ Spec แนะนำให้หลีกเลี่ยงและย้ายเนื้อหาไปที่ features และ app แทน
Processes คือทางหนีทีไล่ (Escape hatches) สำหรับ Multi-page interactions
Layer นี้ถูกทิ้งไว้โดยไม่มีคำนิยามอย่างตั้งใจ Applications ส่วนใหญ่ไม่ควรใช้ Layer นี้ และเก็บ Router-level และ Server-level logic ไว้ที่ App layer พิจารณาใช้ Layer นี้เฉพาะเมื่อ App layer โตขึ้นจนใหญ่เกินไปที่จะดูแลรักษาไหวและต้องการแบ่งเบาภาระ
App
เรื่องราวระดับ App-wide ทั้งหมด ทั้งในแง่เทคนิค (เช่น Context providers) และในแง่ Business (เช่น Analytics)
Layer นี้มักไม่มี Slices เช่นเดียวกับ Shared แต่จะมี Segments โดยตรงแทน
นี่คือ Segments ที่คุณมักจะพบใน Layer นี้:
📁 routes— Router configuration📁 store— Global store configuration📁 styles— Global styles📁 entrypoint— Entrypoint ของ Application code, Framework-specific