เรียนรู้วิธีใช้ Custom Hooks ของ React เพื่อแยกและนำ Logic ของ Component กลับมาใช้ใหม่ ปรับปรุงการดูแลรักษาโค้ด การทดสอบ และสถาปัตยกรรมแอปพลิเคชันโดยรวม
Custom Hooks ของ React: แยก Logic ของ Component เพื่อนำกลับมาใช้ใหม่
React hooks ได้ปฏิวัติวิธีการเขียน React component ของเรา โดยเสนอวิธีที่สง่างามและมีประสิทธิภาพมากขึ้นในการจัดการ state และ side effects ในบรรดา hooks ที่มีอยู่ custom hooks โดดเด่นในฐานะเครื่องมือที่มีประสิทธิภาพสำหรับการแยกและนำ logic ของ component กลับมาใช้ใหม่ บทความนี้ให้คำแนะนำที่ครอบคลุมเกี่ยวกับการทำความเข้าใจและใช้งาน custom hooks ของ React ช่วยให้คุณสร้างแอปพลิเคชันที่ดูแลรักษาได้ง่ายขึ้น ทดสอบได้ดีขึ้น และปรับขนาดได้
Custom Hooks ของ React คืออะไร?
โดยพื้นฐานแล้ว custom hook คือฟังก์ชัน JavaScript ที่ชื่อขึ้นต้นด้วย "use" และสามารถเรียกใช้ hooks อื่นๆ ได้ ช่วยให้คุณสามารถแยก logic ของ component ออกเป็นฟังก์ชันที่นำกลับมาใช้ใหม่ได้ ทำให้ไม่ต้องซ้ำซ้อนกัน และส่งเสริมโครงสร้าง component ที่สะอาดกว่า custom hooks ต่างจาก React component ทั่วไปตรงที่จะไม่แสดง UI ใดๆ แต่จะทำการห่อหุ้ม logic เท่านั้น
ลองนึกภาพว่าเป็นฟังก์ชันที่นำกลับมาใช้ใหม่ได้ ซึ่งสามารถเข้าถึง state และคุณสมบัติ lifecycle ของ React ได้ เป็นวิธีที่ยอดเยี่ยมในการแชร์ stateful logic ระหว่าง component ต่างๆ โดยไม่ต้องอาศัย higher-order components หรือ render props ซึ่งมักจะทำให้เกิดโค้ดที่อ่านและดูแลรักษาได้ยาก
ทำไมต้องใช้ Custom Hooks?
ประโยชน์ของการใช้ custom hooks มีมากมาย:
- การนำกลับมาใช้ใหม่ได้: เขียน logic เพียงครั้งเดียวและนำกลับมาใช้ในหลาย component ซึ่งช่วยลดความซ้ำซ้อนของโค้ดได้อย่างมาก และทำให้แอปพลิเคชันของคุณดูแลรักษาได้ง่ายขึ้น
- การจัดระเบียบโค้ดที่ดีขึ้น: การแยก logic ที่ซับซ้อนออกเป็น custom hooks จะทำให้ component ของคุณสะอาดตาขึ้น ทำให้อ่านและเข้าใจได้ง่ายขึ้น Component ต่างๆ จะมีความมุ่งเน้นมากขึ้นที่ความรับผิดชอบหลักในการแสดงผล
- การทดสอบที่ดีขึ้น: Custom hooks สามารถทดสอบได้อย่างง่ายดายในรูปแบบแยกต่างหาก คุณสามารถทดสอบ logic ของ hook โดยไม่ต้องแสดง component ซึ่งนำไปสู่การทดสอบที่แข็งแกร่งและน่าเชื่อถือยิ่งขึ้น
- การดูแลรักษาที่เพิ่มขึ้น: เมื่อ logic เปลี่ยนแปลง คุณเพียงแค่ต้องอัปเดตที่เดียว ซึ่งก็คือ custom hook แทนที่จะเป็นทุก component ที่ใช้งาน
- ลด Boilerplate: Custom hooks สามารถห่อหุ้มรูปแบบทั่วไปและงานที่ต้องทำซ้ำๆ ลดปริมาณ boilerplate code ที่คุณต้องเขียนใน component ของคุณ
การสร้าง Custom Hook แรกของคุณ
เรามาแสดงตัวอย่างการสร้างและใช้งาน custom hook ด้วยตัวอย่างที่ใช้งานได้จริง นั่นคือ การดึงข้อมูลจาก API
ตัวอย่าง: useFetch
- Hook สำหรับการดึงข้อมูล
สมมติว่าคุณต้องดึงข้อมูลจาก API ต่างๆ ในแอปพลิเคชัน React ของคุณบ่อยครั้ง แทนที่จะทำซ้ำ logic การ fetch ในแต่ละ component คุณสามารถสร้าง hook useFetch
ได้
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController();
const signal = abortController.signal;
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(url, { signal: signal });
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const json = await response.json();
setData(json);
setError(null); // ล้างข้อผิดพลาดก่อนหน้าใดๆ
} catch (error) {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
setError(error);
}
setData(null); // ล้างข้อมูลก่อนหน้าใดๆ
} finally {
setLoading(false);
}
};
fetchData();
return () => {
abortController.abort(); // ฟังก์ชัน Cleanup เพื่อยกเลิกการ fetch เมื่อ unmount หรือ URL เปลี่ยน
};
}, [url]); // รัน effect อีกครั้งเมื่อ URL เปลี่ยน
return { data, loading, error };
}
export default useFetch;
คำอธิบาย:
- ตัวแปร State: Hook นี้ใช้
useState
เพื่อจัดการ state ของข้อมูล, loading และ error - useEffect: Hook
useEffect
ทำการดึงข้อมูลเมื่อ propurl
เปลี่ยนแปลง - การจัดการข้อผิดพลาด: Hook นี้รวมถึงการจัดการข้อผิดพลาดเพื่อจับข้อผิดพลาดที่อาจเกิดขึ้นระหว่างการดำเนินการ fetch มีการตรวจสอบรหัสสถานะเพื่อให้แน่ใจว่าการตอบสนองสำเร็จ
- สถานะ Loading: สถานะ
loading
ใช้เพื่อระบุว่าข้อมูลกำลังถูกดึงอยู่หรือไม่ - AbortController: ใช้ API AbortController เพื่อยกเลิกคำขอ fetch หาก component ถูก unmount หรือ URL เปลี่ยน ซึ่งจะป้องกัน memory leaks
- ค่าที่ส่งคืน: Hook ส่งคืน object ที่มี state
data
,loading
และerror
การใช้ Hook useFetch
ใน Component
ตอนนี้มาดูกันว่าจะใช้ custom hook นี้ใน React component ได้อย่างไร:
import React from 'react';
import useFetch from './useFetch';
function UserList() {
const { data: users, loading, error } = useFetch('https://jsonplaceholder.typicode.com/users');
if (loading) return <p>กำลังโหลดผู้ใช้...</p>;
if (error) return <p>ข้อผิดพลาด: {error.message}</p>;
if (!users) return <p>ไม่พบผู้ใช้.</p>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
);
}
export default UserList;
คำอธิบาย:
- Component นำเข้า hook
useFetch
- Component เรียกใช้ hook ด้วย URL ของ API
- Component ทำการ destructure object ที่ส่งคืนเพื่อเข้าถึง
data
(เปลี่ยนชื่อเป็นusers
),loading
และerror
state - Component แสดงผลเนื้อหาที่แตกต่างกันตาม state
loading
และerror
- หากข้อมูลพร้อมใช้งาน Component จะแสดงรายการผู้ใช้
รูปแบบ Custom Hook ขั้นสูง
นอกเหนือจากการดึงข้อมูลทั่วไป custom hooks สามารถใช้เพื่อห่อหุ้ม logic ที่ซับซ้อนมากขึ้นได้ นี่คือรูปแบบขั้นสูงบางส่วน:
1. การจัดการ State ด้วย useReducer
สำหรับสถานการณ์การจัดการ state ที่ซับซ้อนกว่า คุณสามารถรวม custom hooks เข้ากับ useReducer
ได้ ซึ่งจะช่วยให้คุณจัดการการเปลี่ยนแปลง state ในลักษณะที่คาดเดาได้และเป็นระเบียบมากขึ้น
import { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function useCounter() {
const [state, dispatch] = useReducer(reducer, initialState);
const increment = () => dispatch({ type: 'increment' });
const decrement = () => dispatch({ type: 'decrement' });
return { count: state.count, increment, decrement };
}
export default useCounter;
การใช้งาน:
import React from 'react';
import useCounter from './useCounter';
function Counter() {
const { count, increment, decrement } = useCounter();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
export default Counter;
2. การผสานรวม Context ด้วย useContext
Custom hooks ยังสามารถใช้เพื่อทำให้การเข้าถึง React Context ง่ายขึ้น แทนที่จะใช้ useContext
โดยตรงใน component ของคุณ คุณสามารถสร้าง custom hook ที่ห่อหุ้ม logic การเข้าถึง context ได้
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext'; // สมมติว่าคุณมี ThemeContext
function useTheme() {
return useContext(ThemeContext);
}
export default useTheme;
การใช้งาน:
import React from 'react';
import useTheme from './useTheme';
function MyComponent() {
const { theme, toggleTheme } = useTheme();
return (
<div style={{ backgroundColor: theme.background, color: theme.color }}>
<p>นี่คือ component ของฉัน.</p>
<button onClick={toggleTheme}>สลับ Theme</button>
</div>
);
}
export default MyComponent;
3. Debouncing และ Throttling
Debouncing และ throttling เป็นเทคนิคที่ใช้ควบคุมอัตราที่ฟังก์ชันถูกเรียกใช้ Custom hooks สามารถใช้เพื่อห่อหุ้ม logic นี้ ทำให้ง่ายต่อการใช้เทคนิคเหล่านี้กับ event handlers
import { useState, useEffect, useRef } from 'react';
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
export default useDebounce;
การใช้งาน:
import React, { useState } from 'react';
import useDebounce from './useDebounce';
function SearchInput() {
const [searchValue, setSearchValue] = useState('');
const debouncedSearchValue = useDebounce(searchValue, 500); // Debounce เป็นเวลา 500ms
useEffect(() => {
// ทำการค้นหาด้วย debouncedSearchValue
console.log('กำลังค้นหา:', debouncedSearchValue);
// แทนที่ console.log ด้วย logic การค้นหาจริงของคุณ
}, [debouncedSearchValue]);
const handleChange = (event) => {
setSearchValue(event.target.value);
};
return (
<input
type="text"
value={searchValue}
onChange={handleChange}
placeholder="ค้นหา..."
/>
);
}
export default SearchInput;
แนวทางปฏิบัติที่ดีที่สุดสำหรับการเขียน Custom Hooks
เพื่อให้แน่ใจว่า custom hooks ของคุณมีประสิทธิภาพและดูแลรักษาได้ง่าย ให้ปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดเหล่านี้:
- ขึ้นต้นด้วย "use": ตั้งชื่อ custom hooks ของคุณด้วยคำนำหน้า "use" เสมอ การสังเกตนี้จะบอก React ว่าฟังก์ชันนั้นเป็นไปตามกฎของ hooks และสามารถใช้งานภายใน functional components ได้
- ทำให้มีจุดประสงค์ที่ชัดเจน: Custom hook แต่ละตัวควรมีวัตถุประสงค์ที่ชัดเจนและเฉพาะเจาะจง หลีกเลี่ยงการสร้าง hook ที่ซับซ้อนเกินไปซึ่งจัดการหน้าที่หลายอย่างเกินไป
- ส่งคืนค่าที่มีประโยชน์: ส่งคืน object ที่มีค่าและฟังก์ชันทั้งหมดที่ component ที่ใช้ hook ต้องการ สิ่งนี้ทำให้ hook มีความยืดหยุ่นและนำกลับมาใช้ใหม่ได้มากขึ้น
- จัดการข้อผิดพลาดอย่างสง่างาม: รวมการจัดการข้อผิดพลาดใน custom hooks ของคุณเพื่อป้องกันพฤติกรรมที่ไม่คาดคิดใน component ของคุณ
- พิจารณา Cleanup: ใช้ฟังก์ชัน cleanup ใน
useEffect
เพื่อป้องกัน memory leaks และรับประกันการจัดการทรัพยากรที่เหมาะสม ซึ่งมีความสำคัญอย่างยิ่งเมื่อจัดการกับ subscriptions, timers หรือ event listeners - เขียนการทดสอบ: ทดสอบ custom hooks ของคุณอย่างละเอียดในรูปแบบแยกต่างหากเพื่อให้แน่ใจว่าทำงานตามที่คาดหวัง
- จัดทำเอกสารเกี่ยวกับ Hooks ของคุณ: จัดทำเอกสารที่ชัดเจนสำหรับ custom hooks ของคุณ อธิบายวัตถุประสงค์ การใช้งาน และข้อจำกัดที่อาจเกิดขึ้น
ข้อควรพิจารณาในระดับสากล
เมื่อพัฒนาแอปพลิเคชันสำหรับผู้ชมทั่วโลก ให้คำนึงถึงสิ่งต่อไปนี้:
- การแปลภาษา (i18n) และการปรับให้เหมาะกับท้องถิ่น (l10n): หาก custom hook ของคุณเกี่ยวข้องกับข้อความที่ผู้ใช้เห็นหรือข้อมูล ให้พิจารณาว่าจะมีการแปลภาษาและปรับให้เหมาะกับท้องถิ่นสำหรับภาษาและภูมิภาคต่างๆ ได้อย่างไร ซึ่งอาจเกี่ยวข้องกับการใช้ไลบรารีเช่น
react-intl
หรือi18next
- การจัดรูปแบบวันที่และเวลา: คำนึงถึงรูปแบบวันที่และเวลาที่แตกต่างกันที่ใช้ทั่วโลก ใช้ฟังก์ชันหรือไลบรารีการจัดรูปแบบที่เหมาะสมเพื่อให้แน่ใจว่าวันที่และเวลาจะแสดงอย่างถูกต้องสำหรับผู้ใช้แต่ละราย
- การจัดรูปแบบสกุลเงิน: ในทำนองเดียวกัน ให้จัดการการจัดรูปแบบสกุลเงินอย่างเหมาะสมสำหรับภูมิภาคต่างๆ
- การเข้าถึง (a11y): ตรวจสอบให้แน่ใจว่า custom hooks ของคุณไม่ส่งผลเสียต่อการเข้าถึงแอปพลิเคชันของคุณ พิจารณาผู้ใช้ที่มีความทุพพลภาพและปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดด้านการเข้าถึง
- ประสิทธิภาพ: ตระหนักถึงผลกระทบด้านประสิทธิภาพที่อาจเกิดขึ้นจาก custom hooks ของคุณ โดยเฉพาะอย่างยิ่งเมื่อจัดการกับ logic ที่ซับซ้อนหรือชุดข้อมูลขนาดใหญ่ ปรับโค้ดของคุณให้เหมาะสมเพื่อให้แน่ใจว่าทำงานได้ดีสำหรับผู้ใช้ในตำแหน่งที่แตกต่างกันด้วยความเร็วเครือข่ายที่หลากหลาย
ตัวอย่าง: การจัดรูปแบบวันที่แบบนานาชาติด้วย Custom Hook
import { useState, useEffect } from 'react';
import { DateTimeFormat } from 'intl';
function useFormattedDate(date, locale) {
const [formattedDate, setFormattedDate] = useState('');
useEffect(() => {
try {
const formatter = new DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric',
});
setFormattedDate(formatter.format(date));
} catch (error) {
console.error('ข้อผิดพลาดในการจัดรูปแบบวันที่:', error);
setFormattedDate('Invalid Date');
}
}, [date, locale]);
return formattedDate;
}
export default useFormattedDate;
การใช้งาน:
import React from 'react';
import useFormattedDate from './useFormattedDate';
function MyComponent() {
const today = new Date();
const enDate = useFormattedDate(today, 'en-US');
const frDate = useFormattedDate(today, 'fr-FR');
const deDate = useFormattedDate(today, 'de-DE');
return (
<div>
<p>US Date: {enDate}</p>
<p>French Date: {frDate}</p>
<p>German Date: {deDate}</p>
</div>
);
}
export default MyComponent;
สรุป
Custom hooks ของ React เป็นกลไกที่มีประสิทธิภาพสำหรับการแยกและนำ logic ของ component กลับมาใช้ใหม่ ด้วยการใช้ custom hooks คุณสามารถเขียนโค้ดที่สะอาดกว่า ดูแลรักษาได้ง่ายขึ้น และทดสอบได้ดีขึ้น เมื่อคุณมีความชำนาญกับ React มากขึ้น การเรียนรู้ custom hooks จะช่วยเพิ่มความสามารถของคุณในการสร้างแอปพลิเคชันที่ซับซ้อนและปรับขนาดได้ อย่าลืมปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดและพิจารณาปัจจัยระดับโลกเมื่อพัฒนา custom hooks เพื่อให้แน่ใจว่ามีประสิทธิภาพและสามารถเข้าถึงได้สำหรับผู้ชมที่หลากหลาย โอบรับพลังของ custom hooks และยกระดับทักษะการพัฒนา React ของคุณ!