Page layouts (เลย์เอาต์ของหน้า)
ไกด์นี้จะสำรวจเรื่อง Abstraction ของ Page layout — เมื่อหลายๆ หน้าใช้โครงสร้างรวมๆ เหมือนกัน และต่างกันแค่เนื้อหาหลัก
คำถามของคุณไม่อยู่ในไกด์นี้เหรอ? โพสต์คำถามของคุณโดยการให้ Feedback ในบทความนี้สิ (ปุ่มสีฟ้าทางขวา) แล้วเราจะพิจารณาขยายไกด์นี้ให้!
Simple layout (เลย์เอาต์แบบง่าย)
เลย์เอาต์ที่ง่ายที่สุดก็คือหน้านี้ที่คุณกำลังดูอยู่ไง มันมี Header พร้อม Navigation, Sidebars สองข้าง, และ Footer พร้อม External links ไม่มี Business logic ซับซ้อน และส่วนที่ Dynamic มีแค่ Sidebars กับ Switchers ทางขวาของ Header เท่านั้น เลย์เอาต์แบบนี้วางไว้ใน shared/ui หรือ app/layouts ได้ทั้งก้อนเลย โดยรับ Props เพื่อเติม Content ให้ Sidebars:
import { Link, Outlet } from "react-router-dom";
import { useThemeSwitcher } from "./useThemeSwitcher";
export function Layout({ siblingPages, headings }) {
const [theme, toggleTheme] = useThemeSwitcher();
return (
<div>
<header>
<nav>
<ul>
<li> <Link to="/">Home</Link> </li>
<li> <Link to="/docs">Docs</Link> </li>
<li> <Link to="/blog">Blog</Link> </li>
</ul>
</nav>
<button onClick={toggleTheme}>{theme}</button>
</header>
<main>
<SiblingPageSidebar siblingPages={siblingPages} />
<Outlet /> {/* นี่คือที่ที่เนื้อหาหลักจะถูกวาง */}
<HeadingsSidebar headings={headings} />
</main>
<footer>
<ul>
<li>GitHub</li>
<li>Twitter</li>
</ul>
</footer>
</div>
);
}
export function useThemeSwitcher() {
const [theme, setTheme] = useState("light");
function toggleTheme() {
setTheme(theme === "light" ? "dark" : "light");
}
useEffect(() => {
document.body.classList.remove("light", "dark");
document.body.classList.add(theme);
}, [theme]);
return [theme, toggleTheme] as const;
}
โค้ดของ Sidebars ขอละไว้ให้นักอ่านไปทำเป็นการบ้านนะ 😉
การใช้ Widgets ใน Layout
บางครั้งคุณอาจอยากใส่ Business logic บางอย่างลงใน Layout โดยเฉพาะถ้าคุณใช้ Nested routes ซ้อนลึกๆ กับ Router เช่น React Router ทีนี้คุณจะเก็บ Layout ไว้ใน Shared หรือ Widgets ไม่ได้แล้ว เพราะ กฎการ Import ของ Layers:
Module ใน Slice หนึ่ง สามารถ Import Slice อื่นๆ ได้เฉพาะเมื่อ Slice เหล่านั้นอยู่ใน Layer ที่ต่ำกว่าอย่างเคร่งครัด
ก่อนจะคุยเรื่องวิธีแก้ เราต้องคุยกันก่อนว่ามันเป็นปัญหาจริงๆ รึเปล่า คุณ จำเป็นต้องใช้ Layout นั้นจริงๆ มั้ย และถ้าใช่ มัน จำเป็นมั้ย ที่จะต้องเป็น Widget? ถ้า Business logic ก้อนนั้นถูกใช้ซ้ำแค่ 2-3 หน้า และ Layout เป็นแค่ Wrapper เล็กๆ สำหรับ Widget นั้น ลองพิจารณา 1 ใน 2 ทางเลือกนี้ดู:
-
เขียน Layout แบบ Inline ใน App layer ตรงที่คุณ Config routing เลย
วิธีนี้เยี่ยมมากสำหรับ Router ที่รองรับ Nesting เพราะคุณสามารถจัดกลุ่ม Route และใส่ Layout ให้เฉพาะพวกมันได้ -
ก็แค่ Copy-paste มันซะ
ความอยากที่จะ Abstract โค้ดมักจะถูกให้ค่าเกินจริง โดยเฉพาะกับ Layouts ที่แทบไม่ค่อยเปลี่ยน สักวันหนึ่งถ้าหน้าหนึ่งต้องเปลี่ยน คุณก็แค่แก้หน้านั้นโดยไม่ต้องกระทบหน้าอื่นให้วุ่นวาย ถ้ากลัวใครจะลืมอัปเดตหน้าอื่น ก็เขียน Comment ทิ้งไว้บอกความสัมพันธ์ระหว่างหน้าพวกนั้นได้
ถ้าข้างบนใช้ไม่ได้สักวิธี มี 2 solutions ที่จะใส่ Widget ลงใน Layout:
- ใช้ Render props หรือ Slots
Framework ส่วนใหญ่ยอมให้คุณส่ง UI เข้ามาจากภายนอกได้ ใน React เรียกว่า render props, ใน Vue เรียกว่า slots - ย้าย Layout ไปที่ App layer
คุณยังสามารถเก็บ Layout ของคุณไว้ที่ App layer ได้ด้วย เช่นในapp/layoutsแล้วคุณอยากจะ Compose widget ไหนเข้าไปก็ได้ตามใจชอบ
อ่านเพิ่มเติม
- มีตัวอย่างการสร้าง Layout พร้อม Authentication ด้วย React และ Remix (เทียบเท่า React Router) ใน tutorial