Functional Component — เป็นวิธีการสร้างคอมโพเนนต์ React โดยใช้ฟังก์ชัน JavaScript ทั่วไป ซึ่งกลายเป็นมาตรฐานหลังจาก React 16.8 เปิดตัวและมีการนำฮุคมาใช้ คอมโพเนนต์แบบฟังก์ชันรับ props และส่งคืนอิลิเมนต์ React โดยไม่ต้องสืบทอดจาก React.Component ตามข้อมูลจาก React, 2024 พบว่า 97% ของคอมโพเนนต์ใหม่ในระบบนิเวศ React ถูกเขียนเป็นฟังก์ชัน
ประเด็นสำคัญ
Functional Component คือคอมโพเนนต์ React ที่ถูกกำหนดเป็นฟังก์ชัน ซึ่งรับออบเจกต์ props และส่งคืนอิลิเมนต์ React ก่อน React 16.8 คอมโพเนนต์เหล่านี้ถูกเรียกว่า stateless เพราะไม่สามารถจัดการสเตตภายในได้
ด้วยการนำ ฮุค มาใช้ใน React 16.8 (กุมภาพันธ์ 2019) คอมโพเนนต์แบบฟังก์ชันสามารถเข้าถึงสเตต (useState) เอฟเฟกต์ข้างเคียง (useEffect) บริบท (useContext) และ ref (useRef) คำว่า “stateless” ล้าสมัยแล้ว — คอมโพเนนต์แบบฟังก์ชันสมัยใหม่จัดการสเตตได้อย่างสมบูรณ์
React แนะนำอย่างเป็นทางการให้คอมโพเนนต์แบบฟังก์ชันเป็น วิธีหลัก ในการสร้างคอมโพเนนต์ เอกสารทั้งหมดบน react.dev, ตัวอย่าง, บทช่วยสอน และ API ใหม่ (Server Components, Actions) สร้างขึ้นบนแนวทางแบบฟังก์ชัน คอมโพเนนต์แบบคลาสไม่ได้ถูกลบออก แต่คุณสมบัติใหม่ของ React มีให้ใช้ผ่านฮุคเท่านั้น
คอมโพเนนต์แบบฟังก์ชันที่ง่ายที่สุดคือฟังก์ชันที่ส่งคืน JSX มันรับ props เป็นอาร์กิวเมนต์ ไม่ต้องใช้ constructor, การเรียก super หรือการผูก this Props สามารถเข้าถึงได้ โดยตรงผ่านอาร์กิวเมนต์ของฟังก์ชัน — ไม่ต้องใช้ this.props
// Functional component without state
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Arrow function (common variant)
const Greeting = ({ name }) => (
<h1>Hello, {name}!</h1>
);
เพื่อเพิ่มสเตต ให้ใช้ useState ฮุคส่งคืนทูเพิลของสองค่า: สเตตปัจจุบันและฟังก์ชัน setter แตกต่างจาก this.setState แบบคลาส useState แทนที่ค่าทั้งหมดแทนที่จะรวมกับค่าก่อนหน้า
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
ฮุคเป็นรากฐานของ React สมัยใหม่ ฮุคแต่ละตัวแก้ไขงานเฉพาะ: useState — สเตต, useEffect — เอฟเฟกต์ข้างเคียง, useContext — การเข้าถึงบริบท, useRef — การอ้างอิงอิลิเมนต์ DOM, useCallback และ useMemo — memoization
useEffect แทนที่ componentDidMount, componentDidUpdate และ componentWillUnmount พร้อมกัน อาร์กิวเมนต์แรกคือ callback ที่มีเอฟเฟกต์ อาร์กิวเมนต์ที่สองคืออาร์เรย์ของ dependencies ถ้าอาร์เรย์ว่างเปล่า เอฟเฟกต์จะทำงานครั้งเดียวหลังจาก mount ถ้ามีการระบุ dependencies เอฟเฟกต์จะทำงานเมื่อใดก็ตามที่มีการเปลี่ยนแปลง
import React, { useState, useEffect } from 'react';
function UserData({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let cancelled = false;
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
if (!cancelled) setUser(data);
});
// Cleanup - analog of componentWillUnmount
return () => { cancelled = true; };
}, [userId]); // Dependency
return <div>{user?.name}</div>;
}
ข้อได้เปรียบหลักของคอมโพเนนต์แบบฟังก์ชันคือ การใช้ลอจิกซ้ำ ผ่าน custom hooks ในขณะที่คอมโพเนนต์แบบคลาส ลอจิกถูกทำซ้ำระหว่าง componentDidMount และ componentDidUpdate ในคอมโพเนนต์แบบฟังก์ชัน ลอจิกจะถูกแยกออกเป็นฟังก์ชันแยกต่างหากที่มีคำนำหน้า use Custom hooks เป็นทางเลือกหลักแทน HOC และ render props
React แนะนำ การประกอบ แทนการสืบทอดเพื่อการใช้โค้ดซ้ำระหว่างคอมโพเนนต์ คอมโพเนนต์แบบฟังก์ชันเหมาะสำหรับการประกอบ: คอมโพเนนต์หนึ่งสามารถมีคอมโพเนนต์อื่นหลายตัว โดยส่ง props และ children ให้พวกมัน
แพทเทิร์น children — เมื่อคอมโพเนนต์รับอิลิเมนต์ลูกผ่าน prop children — ถูกใช้งานอย่างเป็นธรรมชาติ: function Layout({ children }) สิ่งที่คล้าย slots ของ Vue ถูกใช้งานผ่าน props คอมโพเนนต์ที่มีชื่อ: function Page({ header, sidebar, content })
การสืบทอดแทบไม่ถูกใช้ในReact แม้แต่คอมโพเนนต์แบบคลาส ซึ่งในทางเทคนิคสนับสนุน extends ก็ไม่ค่อยสร้างลำดับชั้นที่ลึกกว่าหนึ่งระดับ คอมโพเนนต์แบบฟังก์ชันกับการประกอบครอบคลุมความต้องการการใช้โค้ดซ้ำโดยไม่ต้องสืบทอดอย่างสมบูรณ์
คอมโพเนนต์แบบฟังก์ชันมาพร้อมกับกลไกการปรับแต่งในตัว React.memo — เทียบเท่า PureComponent สำหรับฟังก์ชัน: ป้องกันการเรนเดอร์ใหม่ถ้า props ไม่เปลี่ยนแปลง (การเปรียบเทียบระดับพื้นผิว) useMemo แคชผลลัพธ์ของการคำนวณระหว่างการเรนเดอร์
import React, { useMemo } from 'react';
// React.memo - memoize component
const ExpensiveList = React.memo(({ items }) => {
return <ul>
{items.map(item =>
<li key={item.id}>{item.name}</li>
)}
</ul>;
});
// useMemo - caching result
function Dashboard({ transactions }) {
const summary = useMemo(() => {
return transactions.reduce((acc, t) => acc + t.amount, 0);
}, [transactions]);
return <p>Total: {summary}</p>;
}
useCallback memoize ฟังก์ชัน callback เพื่อไม่ให้ถูกสร้างใหม่ทุกครั้งที่เรนเดอร์ นี่เป็นสิ่งสำคัญเมื่อส่งฟังก์ชันไปยังคอมโพเนนต์ลูกที่หุ้มด้วย React.memo: หากไม่มี useCallback คอมโพเนนต์ลูกจะถูกเรนเดอร์ใหม่ทุกครั้งเพราะการอ้างอิงฟังก์ชันเปลี่ยนไป
ความแตกต่างระหว่างแนวทางทั้งสองเกินกว่ารูปแบบไวยากรณ์ คอมโพเนนต์แบบฟังก์ชัน คือ closure: สเตตและเอฟเฟกต์ถูกผูกกับเรนเดอร์เฉพาะ คอมโพเนนต์แบบคลาส ใช้ this: สเตตถูกดึงจาก this.state เสมอ ซึ่งอาจล้าสมัยเมื่อ callback แบบอะซิงโครนัสทำงาน
| ด้าน | ฟังก์ชัน | คลาส |
|---|---|---|
| สเตต | useState (แยกอิสระ) | this.state + this.setState |
| เอฟเฟกต์ | useEffect พร้อม dependencies | componentDidMount + componentDidUpdate |
| บริบท | useContext | Context.Consumer หรือ static contextType |
| การปรับแต่ง | React.memo + useMemo | PureComponent + shouldComponentUpdate |
| การใช้ลอจิกซ้ำ | Custom hooks | HOC / Render props |
| this | ไม่มี | bind ใน constructor |
คอมโพเนนต์แบบฟังก์ชันให้ กระแสการทำงานเชิงเส้น ในคอมโพเนนต์แบบคลาส ลอจิกที่เกี่ยวข้องมักกระจายอยู่หลายเมธอดวงจรชีวิต ตัวอย่างเช่น การสมัครรับ WebSocket ถูกตั้งค่าใน componentDidMount และถูกลบใน componentWillUnmount ในคอมโพเนนต์แบบฟังก์ชัน การทำงานทั้งสองอยู่ใน useEffect เดียวกันกับฟังก์ชันทำความสะอาดที่ส่งคืน
TypeScript ทำงานเป็นธรรมชาติมากกว่ากับคอมโพเนนต์แบบฟังก์ชันเมื่อเทียบกับคลาส การกำหนดชนิด props — อินเทอร์เฟซสำหรับอาร์กิวเมนต์ของฟังก์ชัน ชนิดที่ส่งคืนคือ JSX.Element หรือ ReactNode useState อนุมานชนิดจากค่าเริ่มต้นโดยอัตโนมัติ
import React, { useState } from 'react';
interface ButtonProps {
label: string;
variant?: 'primary' | 'secondary';
onClick: () => void;
}
const Button: React.FC<ButtonProps> =
({ label, variant = 'primary', onClick }) => (
<button
className={`btn btn-${variant}`}
onClick={onClick}
>
{label}
</button>
);
TypeScript สมัยใหย่ช่วยให้กำหนดชนิด useReducer ด้วยชนิด union แบบแยกแยะ, Generics ใน custom hooks และการตรวจสอบ children อย่างเข้มงวดผ่าน React.PropsWithChildren คอมโพเนนต์แบบฟังก์ชันกับ TypeScript ให้ความปลอดภัยของชนิดอย่างสมบูรณ์โดยไม่มีนามธรรมเพิ่มเติม
คำถามที่พบบ่อย
คอมโพเนนต์แบบฟังก์ชัน ง่ายกว่า: ไม่มี constructor, bind, this หรือเมธอดวงจรชีวิตที่กระจายอยู่ ฮุคช่วยให้จัดกลุ่มลอจิกที่เกี่ยวข้องได้ React ลงทุนทรัพยากรในการพัฒนาฮุค ไม่ใช่คลาส Server Components และคุณสมบัติใหม่มีให้ใช้เฉพาะในฟังก์ชันเท่านั้น
ได้ ผ่าน useEffect โดยมีตัวจับเวลาภายในและฟังก์ชันทำความสะอาดสำหรับ clearInterval โปรดระวัง closure: ถ้าคุณเพิ่มค่าตัวนับ ให้ใช้รูปแบบฟังก์ชันของ setState: setCount(c => c + 1) เพื่อไม่ให้ขึ้นอยู่กับค่าเก่าใน closure
ใช้ useRef เพื่อเก็บค่าก่อนหน้า อัปเดต ref ใน useEffect หลังจากการเรนเดอร์แต่ละครั้ง Custom hook usePrevious ช่วยในการเปรียบเทียบ: const prev = usePrevious(value);
React.memo คือ HOC สำหรับ memoize ทั้งคอมโพเนนต์: ป้องกันการเรนเดอร์ใหม่ถ้า props ไม่เปลี่ยนแปลง useMemo คือฮุคสำหรับ memoize ค่าเฉพาะภายในคอมโพเนนต์: แคชผลลัพธ์ของฟังก์ชันระหว่างการเรนเดอร์ตามอาร์เรย์ของ dependencies
วิธีที่ง่ายที่สุดคือ useState ที่มีการเริ่มต้นจาก props + useEffect ที่มี dependency กับ props เพื่อการซิงค์ สำหรับกรณีที่พบไม่บ่อย React แนะนำให้ใช้ key เพื่อบังคับให้สร้างคอมโพเนนต์ใหม่เมื่อข้อมูลนำเข้าเปลี่ยนแปลง
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ