เจาะลึกสถาปัตยกรรมคอมโพเนนต์ของ React เปรียบเทียบระหว่าง Composition และ Inheritance เรียนรู้ว่าทำไม React ถึงนิยมใช้ Composition และสำรวจรูปแบบต่างๆ เช่น HOCs, Render Props และ Hooks เพื่อสร้างคอมโพเนนต์ที่ยืดหยุ่นและนำกลับมาใช้ใหม่ได้
สถาปัตยกรรมคอมโพเนนต์ของ React: ทำไม Composition ถึงดีกว่า Inheritance
ในโลกของการพัฒนาซอฟต์แวร์ สถาปัตยกรรมคือสิ่งสำคัญที่สุด วิธีที่เราจัดโครงสร้างโค้ดจะกำหนดความสามารถในการขยายระบบ (scalability), การบำรุงรักษา (maintainability) และการนำกลับมาใช้ใหม่ (reusability) สำหรับนักพัฒนาที่ทำงานกับ React หนึ่งในการตัดสินใจทางสถาปัตยกรรมที่สำคัญที่สุดคือการแชร์ Logic และ UI ระหว่างคอมโพเนนต์ ซึ่งนำเราไปสู่การถกเถียงสุดคลาสสิกในการเขียนโปรแกรมเชิงวัตถุ ที่ถูกนำมาตีความใหม่ในโลกของคอมโพเนนต์เบสของ React นั่นคือ: Composition vs. Inheritance
หากคุณมาจากสายภาษาโปรแกรมเชิงวัตถุแบบดั้งเดิมอย่าง Java หรือ C++ การสืบทอดคุณสมบัติ (Inheritance) อาจเป็นตัวเลือกแรกที่คุณนึกถึง มันเป็นแนวคิดที่ทรงพลังสำหรับการสร้างความสัมพันธ์แบบ 'is-a' (เป็น) อย่างไรก็ตาม เอกสารทางการของ React ได้ให้คำแนะนำที่ชัดเจนและหนักแน่นว่า: "ที่ Facebook เราใช้ React ในคอมโพเนนท์หลายพันตัว และเรายังไม่เคยเจอกรณีการใช้งานใดๆ ที่เราจะแนะนำให้สร้างลำดับชั้นการสืบทอดคอมโพเนนท์เลย"
โพสต์นี้จะสำรวจตัวเลือกทางสถาปัตยกรรมนี้อย่างละเอียด เราจะมาทำความเข้าใจว่า Inheritance และ Composition หมายถึงอะไรในบริบทของ React, แสดงให้เห็นว่าทำไม Composition ถึงเป็นแนวทางที่เป็นธรรมชาติและดีกว่า และสำรวจรูปแบบที่ทรงพลังต่างๆ ตั้งแต่ Higher-Order Components ไปจนถึง Hooks สมัยใหม่ ที่ทำให้ Composition กลายเป็นเพื่อนที่ดีที่สุดของนักพัฒนาในการสร้างแอปพลิเคชันที่แข็งแกร่งและยืดหยุ่นสำหรับผู้ใช้งานทั่วโลก
ทำความเข้าใจแนวทางดั้งเดิม: Inheritance คืออะไร?
Inheritance เป็นหลักการสำคัญของการเขียนโปรแกรมเชิงวัตถุ (Object-Oriented Programming - OOP) มันอนุญาตให้คลาสใหม่ (subclass หรือ child) ได้รับคุณสมบัติและเมธอดของคลาสที่มีอยู่แล้ว (superclass หรือ parent) ซึ่งสร้างความสัมพันธ์แบบ 'is-a' ที่ผูกมัดกันอย่างแน่นหนา ตัวอย่างเช่น GoldenRetriever
เป็น Dog
ซึ่ง เป็น Animal
Inheritance ในบริบทที่ไม่ใช่ React
เรามาดูตัวอย่างคลาส JavaScript ง่ายๆ เพื่อให้เข้าใจแนวคิดนี้มากขึ้น:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Calls the parent constructor
this.breed = breed;
}
speak() { // Overrides the parent method
console.log(`${this.name} barks.`);
}
fetch() {
console.log(`${this.name} is fetching the ball!`);
}
}
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.speak(); // Output: "Buddy barks."
myDog.fetch(); // Output: "Buddy is fetching the ball!"
ในโมเดลนี้ คลาส Dog
จะได้รับ property name
และเมธอด speak
จาก Animal
โดยอัตโนมัติ นอกจากนี้ยังสามารถเพิ่มเมธอดของตัวเอง (fetch
) และเขียนทับ (override) เมธอดที่มีอยู่แล้วได้ ซึ่งสิ่งนี้สร้างลำดับชั้นที่ตายตัว
ทำไม Inheritance ถึงไม่เหมาะกับ React
แม้ว่าโมเดล 'is-a' นี้จะใช้ได้กับโครงสร้างข้อมูลบางประเภท แต่มันสร้างปัญหาสำคัญเมื่อนำมาใช้กับ UI components ใน React:
- การผูกมัดที่แน่นหนา (Tight Coupling): เมื่อคอมโพเนนต์สืบทอดคุณสมบัติจากคอมโพเนนต์พื้นฐาน (base component) มันจะผูกติดกับการ υλοποίηση ของ parent อย่างแน่นหนา การเปลี่ยนแปลงในคอมโพเนนต์พื้นฐานอาจทำให้ child components หลายตัวที่อยู่ถัดไปพังโดยไม่คาดคิด ทำให้การแก้ไขโค้ด (refactoring) และการบำรุงรักษากลายเป็นกระบวนการที่เปราะบาง
- การแชร์ Logic ที่ไม่ยืดหยุ่น: จะทำอย่างไรถ้าคุณต้องการแชร์ฟังก์ชันการทำงานบางอย่าง เช่น การดึงข้อมูล (data fetching) กับคอมโพเนนต์ที่ไม่ได้อยู่ในลำดับชั้น 'is-a' เดียวกัน? ตัวอย่างเช่น
UserProfile
และProductList
อาจต้องการดึงข้อมูลทั้งคู่ แต่ไม่มีเหตุผลเลยที่ทั้งสองจะต้องสืบทอดคุณสมบัติจากDataFetchingComponent
ร่วมกัน - ปัญหานรกของการส่ง Props ต่อๆ กัน (Prop-Drilling Hell): ในลำดับชั้นการสืบทอดที่ลึก การส่ง props จากคอมโพเนนต์ระดับบนสุดไปยัง child ที่ซ้อนกันลึกๆ จะกลายเป็นเรื่องยาก คุณอาจต้องส่ง props ผ่านคอมโพเนนท์กลางที่ไม่ได้ใช้งานมันเลย ซึ่งนำไปสู่โค้ดที่สับสนและบวม
- ปัญหา "กอริลลากับกล้วย" (The "Gorilla-Banana Problem"): คำพูดที่มีชื่อเสียงจาก Joe Armstrong ผู้เชี่ยวชาญด้าน OOP อธิบายปัญหานี้ได้อย่างสมบูรณ์แบบ: "คุณต้องการกล้วย แต่สิ่งที่คุณได้กลับมาคือกอริลลาที่ถือกล้วยและป่าทั้งป่า" ด้วย Inheritance คุณไม่สามารถเลือกเฉพาะส่วนของฟังก์ชันการทำงานที่ต้องการได้ คุณถูกบังคับให้ต้องนำทั้ง superclass ติดมาด้วย
เนื่องจากปัญหาเหล่านี้ ทีม React จึงออกแบบไลบรารีโดยใช้กระบวนทัศน์ที่ยืดหยุ่นและทรงพลังกว่า นั่นคือ: composition
ยอมรับวิถีแห่ง React: พลังของ Composition
Composition เป็นหลักการออกแบบที่สนับสนุนความสัมพันธ์แบบ 'has-a' (มี) หรือ 'uses-a' (ใช้) แทนที่คอมโพเนนต์หนึ่งจะ เป็น อีกคอมโพเนนต์หนึ่ง แต่มันจะ มี คอมโพเนนต์อื่น ๆ หรือ ใช้ ฟังก์ชันการทำงานของคอมโพเนนต์เหล่านั้น คอมโพเนนต์จะถูกมองว่าเป็นส่วนประกอบ (building blocks) เหมือนตัวต่อ LEGO ที่สามารถนำมารวมกันในรูปแบบต่างๆ เพื่อสร้าง UI ที่ซับซ้อนโดยไม่ต้องถูกจำกัดอยู่ในลำดับชั้นที่ตายตัว
โมเดล Composition ของ React นั้นมีความหลากหลายอย่างไม่น่าเชื่อ และปรากฏในรูปแบบสำคัญหลายอย่าง เรามาสำรวจรูปแบบเหล่านั้นกัน ตั้งแต่แบบพื้นฐานที่สุดไปจนถึงแบบที่ทันสมัยและทรงพลังที่สุด
เทคนิคที่ 1: การครอบด้วย `props.children`
รูปแบบที่ตรงไปตรงมาที่สุดของ composition คือการครอบ (containment) ซึ่งคือการที่คอมโพเนนต์ทำหน้าที่เป็นภาชนะหรือ 'กล่อง' ทั่วไป และเนื้อหาของมันถูกส่งมาจากคอมโพเนนต์แม่ React มี prop พิเศษที่สร้างมาเพื่อการนี้โดยเฉพาะ นั่นคือ props.children
ลองจินตนาการว่าคุณต้องการคอมโพเนนต์ `Card` ที่สามารถครอบเนื้อหาใดๆ ก็ได้ด้วยเส้นขอบและเงาที่เหมือนกัน แทนที่จะสร้าง `TextCard`, `ImageCard` และ `ProfileCard` ผ่านการสืบทอด คุณสร้างคอมโพเนนต์ `Card` ทั่วไปเพียงอันเดียว
// Card.js - A generic container component
function Card(props) {
return (
<div className="card">
{props.children}
</div>
);
}
// App.js - Using the Card component
function App() {
return (
<div>
<Card>
<h1>Welcome!</h1>
<p>This content is inside a Card component.</p>
</Card>
<Card>
<img src="/path/to/image.jpg" alt="An example image" />
<p>This is an image card.</p>
</Card>
</div>
);
}
ในที่นี้ คอมโพเนนต์ `Card` ไม่รู้หรือไม่สนใจว่าข้างในมีอะไร มันแค่ทำหน้าที่ให้สไตล์ของกรอบเท่านั้น เนื้อหาที่อยู่ระหว่างแท็กเปิดและปิด <Card>
จะถูกส่งเป็น props.children
โดยอัตโนมัติ นี่เป็นตัวอย่างที่สวยงามของการแยกส่วน (decoupling) และการนำกลับมาใช้ใหม่
เทคนิคที่ 2: การสร้างความเฉพาะเจาะจงด้วย Props
บางครั้ง คอมโพเนนต์ต้องการ 'ช่อง' หลายช่องเพื่อให้คอมโพเนนต์อื่นมาเติม แม้ว่าคุณจะใช้ `props.children` ได้ แต่วิธีที่ชัดเจนและมีโครงสร้างมากกว่าคือการส่งคอมโพเนนท์เป็น props ปกติ รูปแบบนี้มักเรียกว่าการสร้างความเฉพาะเจาะจง (specialization)
พิจารณาคอมโพเนนต์ `Modal` โดยทั่วไปแล้ว modal จะมีส่วนหัวข้อ (title), ส่วนเนื้อหา (content) และส่วนการกระทำ (actions) (เช่น ปุ่ม "Confirm" หรือ "Cancel") เราสามารถออกแบบ `Modal` ของเราให้รับส่วนต่างๆ เหล่านี้เป็น props ได้
// Modal.js - A more specialized container
function Modal(props) {
return (
<div className="modal-backdrop">
<div className="modal-content">
<div className="modal-header">{props.title}</div>
<div className="modal-body">{props.body}</div>
<div className="modal-footer">{props.actions}</div>
</div>
</div>
);
}
// App.js - Using the Modal with specific components
function App() {
const confirmationTitle = <h2>Confirm Action</h2>;
const confirmationBody = <p>Are you sure you want to proceed with this action?</p>;
const confirmationActions = (
<div>
<button>Confirm</button>
<button>Cancel</button>
</div>
);
return (
<Modal
title={confirmationTitle}
body={confirmationBody}
actions={confirmationActions}
/>
);
}
ในตัวอย่างนี้ `Modal` เป็นคอมโพเนนต์ layout ที่สามารถนำกลับมาใช้ใหม่ได้อย่างสูง เราสร้างความเฉพาะเจาะจงให้มันโดยการส่ง JSX elements ที่ต้องการสำหรับ `title`, `body` และ `actions` ซึ่งยืดหยุ่นกว่าการสร้าง subclass อย่าง `ConfirmationModal` และ `WarningModal` มาก เราเพียงแค่ประกอบ `Modal` ด้วยเนื้อหาที่แตกต่างกันไปตามความต้องการ
เทคนิคที่ 3: Higher-Order Components (HOCs)
สำหรับการแชร์ logic ที่ไม่ใช่ UI เช่น การดึงข้อมูล, การยืนยันตัวตน หรือการบันทึก log ในอดีตนักพัฒนา React มักจะใช้รูปแบบที่เรียกว่า Higher-Order Components (HOCs) แม้ว่าใน React สมัยใหม่จะถูกแทนที่ด้วย Hooks ไปเป็นส่วนใหญ่แล้ว แต่การทำความเข้าใจ HOCs ก็ยังเป็นสิ่งสำคัญ เนื่องจากเป็นขั้นตอนวิวัฒนาการที่สำคัญในเรื่องราวของ Composition ใน React และยังคงมีอยู่ในโค้ดเบสจำนวนมาก
HOC คือฟังก์ชันที่รับคอมโพเนนต์เป็นอาร์กิวเมนต์และส่งคืนคอมโพเนนต์ใหม่ที่ถูกปรับปรุงแล้ว
ลองสร้าง HOC ที่ชื่อว่า `withLogger` ซึ่งจะ log props ของคอมโพเนนต์ทุกครั้งที่มีการอัปเดต ซึ่งมีประโยชน์สำหรับการดีบัก
// withLogger.js - The HOC
import React, { useEffect } from 'react';
function withLogger(WrappedComponent) {
// It returns a new component...
return function EnhancedComponent(props) {
useEffect(() => {
console.log('Component updated with new props:', props);
}, [props]);
// ... that renders the original component with the original props.
return <WrappedComponent {...props} />;
};
}
// MyComponent.js - A component to be enhanced
function MyComponent({ name, age }) {
return (
<div>
<h1>Hello, {name}!</h1>
<p>You are {age} years old.</p>
</div>
);
}
// Exporting the enhanced component
export default withLogger(MyComponent);
ฟังก์ชัน `withLogger` จะครอบ `MyComponent` เพื่อเพิ่มความสามารถในการ logging โดยไม่ต้องแก้ไขโค้ดภายในของ `MyComponent` เลย เราสามารถนำ HOC นี้ไปใช้กับคอมโพเนนต์อื่นใดก็ได้เพื่อเพิ่มฟีเจอร์ logging แบบเดียวกัน
ความท้าทายของ HOCs:
- Wrapper Hell: การใช้ HOCs หลายตัวกับคอมโพเนนต์เดียวอาจทำให้เกิดคอมโพเนนต์ซ้อนกันลึกใน React DevTools (เช่น `withAuth(withRouter(withLogger(MyComponent)))`) ทำให้การดีบักทำได้ยาก
- การชนกันของชื่อ Prop (Prop Naming Collisions): หาก HOC ส่ง prop (เช่น `data`) ที่ถูกใช้โดยคอมโพเนนต์ที่ถูกครอบอยู่แล้ว มันอาจถูกเขียนทับโดยไม่ได้ตั้งใจ
- Logic ที่ซ่อนเร้น (Implicit Logic): ไม่ใช่เรื่องง่ายเสมอไปที่จะดูจากโค้ดของคอมโพเนนต์ว่า props ของมันมาจากไหน เพราะ logic นั้นซ่อนอยู่ภายใน HOC
เทคนิคที่ 4: Render Props
รูปแบบ Render Prop เกิดขึ้นเพื่อแก้ปัญหาบางอย่างของ HOCs โดยเสนอวิธีการแชร์ logic ที่ชัดเจนกว่า
คอมโพเนนต์ที่มี render prop จะรับฟังก์ชันเป็น prop (โดยปกติจะชื่อ `render`) และเรียกใช้ฟังก์ชันนั้นเพื่อกำหนดว่าจะ render อะไร โดยส่ง state หรือ logic ใดๆ เป็นอาร์กิวเมนต์ไปยังฟังก์ชันนั้น
ลองสร้างคอมโพเนนต์ `MouseTracker` ที่ติดตามพิกัด X และ Y ของเมาส์ และทำให้พิกัดเหล่านั้นพร้อมใช้งานสำหรับคอมโพเนนต์ใดๆ ที่ต้องการใช้
// MouseTracker.js - Component with a render prop
import React, { useState, useEffect } from 'react';
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);
// Call the render function with the state
return render(position);
}
// App.js - Using the MouseTracker
function App() {
return (
<div>
<h1>Move your mouse around!</h1>
<MouseTracker
render={mousePosition => (
<p>The current mouse position is ({mousePosition.x}, {mousePosition.y})</p>
)}
/>
</div>
);
}
ในที่นี้ `MouseTracker` จะห่อหุ้ม logic ทั้งหมดสำหรับการติดตามการเคลื่อนไหวของเมาส์ แต่มันไม่ได้ render อะไรด้วยตัวเอง แต่จะมอบหมาย logic การ render ให้กับ prop `render` ของมันแทน ซึ่งวิธีนี้ชัดเจนกว่า HOCs เพราะคุณสามารถเห็นได้ทันทีว่าข้อมูล `mousePosition` มาจากไหนภายใน JSX
prop `children` ก็สามารถใช้เป็นฟังก์ชันได้เช่นกัน ซึ่งเป็นรูปแบบที่นิยมและสวยงามของแพทเทิร์นนี้:
// Using children as a function
<MouseTracker>
{mousePosition => (
<p>The current mouse position is ({mousePosition.x}, {mousePosition.y})</p>
)}
</MouseTracker>
เทคนิคที่ 5: Hooks (แนวทางสมัยใหม่ที่แนะนำ)
Hooks ซึ่งเปิดตัวใน React 16.8 ได้ปฏิวัติวิธีการเขียนคอมโพเนนต์ React ของเรา มันช่วยให้คุณสามารถใช้ state และฟีเจอร์อื่นๆ ของ React ใน functional components ได้ และที่สำคัญที่สุดคือ custom Hooks เป็นวิธีแก้ปัญหาที่สวยงามและตรงไปตรงมาที่สุดสำหรับการแชร์ stateful logic ระหว่างคอมโพเนนต์
Hooks แก้ปัญหาของ HOCs และ Render Props ด้วยวิธีที่สะอาดกว่ามาก เรามาแก้ไขตัวอย่าง `MouseTracker` ของเราให้เป็น custom hook ที่เรียกว่า `useMousePosition`
// hooks/useMousePosition.js - A custom Hook
import { useState, useEffect } from 'react';
export function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []); // Empty dependency array means this effect runs only once
return position;
}
// DisplayMousePosition.js - A component using the Hook
import { useMousePosition } from './hooks/useMousePosition';
function DisplayMousePosition() {
const position = useMousePosition(); // Just call the hook!
return (
<p>
The mouse position is ({position.x}, {position.y})
</p>
);
}
// Another component, maybe an interactive element
import { useMousePosition } from './hooks/useMousePosition';
function InteractiveBox() {
const { x, y } = useMousePosition();
const style = {
position: 'absolute',
top: y - 25, // Center the box on the cursor
left: x - 25,
width: '50px',
height: '50px',
backgroundColor: 'lightblue',
};
return <div style={style} />;
}
นี่คือการปรับปรุงครั้งใหญ่ ไม่มี 'wrapper hell', ไม่มีการชนกันของชื่อ prop และไม่มีฟังก์ชัน render prop ที่ซับซ้อน logic ถูกแยกออกจากกันอย่างสมบูรณ์เป็นฟังก์ชันที่นำกลับมาใช้ใหม่ได้ (`useMousePosition`) และคอมโพเนนต์ใดๆ ก็สามารถ 'hook' เข้ากับ stateful logic นั้นได้ด้วยโค้ดเพียงบรรทัดเดียวที่ชัดเจน Custom Hooks คือที่สุดของการแสดงออกถึง Composition ใน React สมัยใหม่ ซึ่งช่วยให้คุณสามารถสร้างคลังของ logic blocks ที่นำกลับมาใช้ใหม่ได้เอง
เปรียบเทียบสั้นๆ: Composition vs. Inheritance ใน React
เพื่อสรุปความแตกต่างที่สำคัญในบริบทของ React นี่คือตารางเปรียบเทียบโดยตรง:
แง่มุม | Inheritance (รูปแบบที่ไม่ควรใช้ใน React) | Composition (รูปแบบที่แนะนำใน React) |
---|---|---|
ความสัมพันธ์ | ความสัมพันธ์แบบ 'is-a' (เป็น) คอมโพเนนต์ที่เฉพาะเจาะจง เป็น เวอร์ชันหนึ่งของคอมโพเนนต์พื้นฐาน | ความสัมพันธ์แบบ 'has-a' (มี) หรือ 'uses-a' (ใช้) คอมโพเนนต์ที่ซับซ้อน มี คอมโพเนนต์ขนาดเล็กอยู่ภายใน หรือ ใช้ logic ที่แชร์ร่วมกัน |
การผูกมัด (Coupling) | สูง Child components ผูกติดกับการทำงานของ parent อย่างแน่นหนา | ต่ำ คอมโพเนนต์เป็นอิสระต่อกันและสามารถนำกลับมาใช้ใหม่ในบริบทที่แตกต่างกันได้โดยไม่ต้องแก้ไข |
ความยืดหยุ่น | ต่ำ ลำดับชั้นแบบคลาสที่ตายตัวทำให้ยากต่อการแชร์ logic ข้ามไปยัง component tree อื่นๆ | สูง Logic และ UI สามารถนำมารวมและใช้ซ้ำได้ในหลากหลายวิธี เหมือนตัวต่อ |
การนำโค้ดกลับมาใช้ใหม่ | จำกัดอยู่แค่ในลำดับชั้นที่กำหนดไว้ล่วงหน้า คุณได้ "กอริลลา" ทั้งตัว ทั้งๆ ที่ต้องการแค่ "กล้วย" | ยอดเยี่ยม คอมโพเนนต์และ hooks ขนาดเล็กที่ทำงานเฉพาะทางสามารถนำไปใช้ได้ทั่วทั้งแอปพลิเคชัน |
รูปแบบการเขียนของ React | ไม่แนะนำโดยทีม React อย่างเป็นทางการ | เป็นแนวทางที่แนะนำและเป็นธรรมชาติสำหรับการสร้างแอปพลิเคชัน React |
สรุป: คิดแบบ Composition
การถกเถียงระหว่าง composition และ inheritance เป็นหัวข้อพื้นฐานในการออกแบบซอฟต์แวร์ แม้ว่า inheritance จะมีที่ยืนในโลก OOP แบบคลาสสิก แต่ธรรมชาติของการพัฒนา UI ที่เป็นแบบไดนามิกและใช้คอมโพเนนต์เป็นหลัก ทำให้มันไม่เหมาะกับ React ไลบรารีนี้ถูกออกแบบมาเพื่อรองรับ composition โดยพื้นฐาน
การเลือกใช้ composition จะทำให้คุณได้:
- ความยืดหยุ่น: ความสามารถในการผสมผสาน UI และ logic ได้ตามต้องการ
- การบำรุงรักษาที่ง่าย: คอมโพเนนต์ที่ผูกมัดกันอย่างหลวมๆ นั้นง่ายต่อการทำความเข้าใจ ทดสอบ และแก้ไขโค้ดโดยไม่กระทบส่วนอื่น
- ความสามารถในการขยายระบบ: แนวคิดแบบ composition ส่งเสริมการสร้าง design system ที่ประกอบด้วยคอมโพเนนต์และ hooks ขนาดเล็กที่นำกลับมาใช้ใหม่ได้ ซึ่งสามารถนำไปสร้างแอปพลิเคชันขนาดใหญ่และซับซ้อนได้อย่างมีประสิทธิภาพ
ในฐานะนักพัฒนา React ระดับโลก การเรียนรู้ Composition ไม่ใช่แค่การทำตามแนวทางปฏิบัติที่ดีที่สุด แต่มันคือการทำความเข้าใจปรัชญาหลักที่ทำให้ React เป็นเครื่องมือที่ทรงพลังและมีประสิทธิภาพ เริ่มต้นด้วยการสร้างคอมโพเนนต์ขนาดเล็กที่ทำงานเฉพาะทาง ใช้ `props.children` สำหรับ container ทั่วไปและใช้ props สำหรับการสร้างความเฉพาะเจาะจง สำหรับการแชร์ logic ให้เลือกใช้ custom Hooks เป็นอันดับแรก เมื่อคุณคิดแบบ composition คุณก็จะอยู่บนเส้นทางที่ถูกต้องสู่การสร้างแอปพลิเคชัน React ที่สวยงาม แข็งแกร่ง และขยายขนาดได้ ซึ่งจะคงอยู่ต่อไปอีกนาน