Khai phá sức mạnh của logic tái sử dụng trong ứng dụng React của bạn với custom hooks. Học cách tạo và tận dụng custom hooks để có mã nguồn sạch hơn và dễ bảo trì hơn.
Custom Hooks: Các Mẫu Logic Tái Sử Dụng trong React
React Hooks đã cách mạng hóa cách chúng ta viết các thành phần React bằng cách giới thiệu các tính năng về trạng thái và vòng đời cho các thành phần chức năng. Trong số nhiều lợi ích mà chúng mang lại, custom hooks nổi bật như một cơ chế mạnh mẽ để trích xuất và tái sử dụng logic trên nhiều thành phần. Bài đăng blog này sẽ đi sâu vào thế giới của custom hooks, khám phá lợi ích, cách tạo và sử dụng chúng với các ví dụ thực tế.
Custom Hooks là gì?
Về cơ bản, một custom hook là một hàm JavaScript bắt đầu bằng từ "use" và có thể gọi các hook khác. Chúng cho phép bạn trích xuất logic của thành phần vào các hàm có thể tái sử dụng. Đây là một cách mạnh mẽ để chia sẻ logic có trạng thái, các hiệu ứng phụ hoặc các hành vi phức tạp khác giữa các thành phần mà không cần dùng đến render props, higher-order components hoặc các mẫu phức tạp khác.
Các đặc điểm chính của Custom Hooks:
- Quy ước đặt tên: Custom hooks phải bắt đầu bằng từ "use". Điều này báo hiệu cho React rằng hàm này chứa các hook và nên tuân theo các quy tắc của hooks.
- Khả năng tái sử dụng: Mục đích chính là đóng gói logic có thể tái sử dụng, giúp dễ dàng chia sẻ chức năng giữa các thành phần.
- Logic có trạng thái: Custom hooks có thể quản lý trạng thái riêng của chúng bằng cách sử dụng hook
useState
, cho phép chúng đóng gói các hành vi có trạng thái phức tạp. - Hiệu ứng phụ: Chúng cũng có thể thực hiện các hiệu ứng phụ bằng cách sử dụng hook
useEffect
, cho phép tích hợp với các API bên ngoài, tìm nạp dữ liệu và hơn thế nữa. - Khả năng kết hợp: Custom hooks có thể gọi các hook khác, cho phép bạn xây dựng logic phức tạp bằng cách kết hợp các hook nhỏ hơn, tập trung hơn.
Lợi ích của việc sử dụng Custom Hooks
Custom hooks mang lại một số lợi thế đáng kể trong việc phát triển React:
- Tái sử dụng mã nguồn: Lợi ích rõ ràng nhất là khả năng tái sử dụng logic trên nhiều thành phần. Điều này làm giảm sự trùng lặp mã và thúc đẩy một codebase theo nguyên tắc DRY (Don't Repeat Yourself).
- Cải thiện khả năng đọc: Bằng cách trích xuất logic phức tạp vào các custom hooks riêng biệt, các thành phần của bạn trở nên sạch sẽ và dễ hiểu hơn. Logic cốt lõi của thành phần vẫn tập trung vào việc kết xuất giao diện người dùng.
- Tăng cường khả năng bảo trì: Khi logic được đóng gói trong các custom hooks, các thay đổi và sửa lỗi có thể được áp dụng ở một vị trí duy nhất, giảm nguy cơ gây ra lỗi trong nhiều thành phần.
- Khả năng kiểm thử: Custom hooks có thể được kiểm thử một cách độc lập, đảm bảo rằng logic có thể tái sử dụng hoạt động chính xác độc lập với các thành phần sử dụng chúng.
- Đơn giản hóa các thành phần: Custom hooks giúp làm gọn gàng các thành phần, làm cho chúng ít dài dòng hơn và tập trung hơn vào mục đích chính của chúng.
Tạo Custom Hook đầu tiên của bạn
Hãy minh họa việc tạo một custom hook bằng một ví dụ thực tế: một hook theo dõi kích thước cửa sổ.
Ví dụ: useWindowSize
Hook này sẽ trả về chiều rộng và chiều cao hiện tại của cửa sổ trình duyệt. Nó cũng sẽ cập nhật các giá trị này khi cửa sổ được thay đổi kích thước.
import { useState, useEffect } from 'react';
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener('resize', handleResize);
// Remove event listener on cleanup
return () => window.removeEventListener('resize', handleResize);
}, []); // Empty array ensures that effect is only run on mount
return windowSize;
}
export default useWindowSize;
Giải thích:
- Nhập các Hook cần thiết: Chúng ta nhập
useState
vàuseEffect
từ React. - Định nghĩa Hook: Chúng ta tạo một hàm có tên
useWindowSize
, tuân thủ quy ước đặt tên. - Khởi tạo trạng thái: Chúng ta sử dụng
useState
để khởi tạo trạng tháiwindowSize
với chiều rộng và chiều cao ban đầu của cửa sổ. - Thiết lập trình lắng nghe sự kiện: Chúng ta sử dụng
useEffect
để thêm một trình lắng nghe sự kiện resize vào cửa sổ. Khi cửa sổ được thay đổi kích thước, hàmhandleResize
sẽ cập nhật trạng tháiwindowSize
. - Dọn dẹp: Chúng ta trả về một hàm dọn dẹp từ
useEffect
để xóa trình lắng nghe sự kiện khi thành phần bị gỡ bỏ. Điều này ngăn chặn rò rỉ bộ nhớ. - Trả về giá trị: Hook trả về đối tượng
windowSize
, chứa chiều rộng và chiều cao hiện tại của cửa sổ.
Sử dụng Custom Hook trong một Component
Bây giờ chúng ta đã tạo xong custom hook, hãy xem cách sử dụng nó trong một thành phần React.
import React from 'react';
import useWindowSize from './useWindowSize';
function MyComponent() {
const { width, height } = useWindowSize();
return (
Window width: {width}px
Window height: {height}px
);
}
export default MyComponent;
Giải thích:
- Nhập Hook: Chúng ta nhập custom hook
useWindowSize
. - Gọi Hook: Chúng ta gọi hook
useWindowSize
bên trong thành phần. - Truy cập các giá trị: Chúng ta sử dụng destructuring để lấy các giá trị
width
vàheight
từ đối tượng được trả về. - Kết xuất các giá trị: Chúng ta kết xuất các giá trị chiều rộng và chiều cao trong giao diện người dùng của thành phần.
Bất kỳ thành phần nào sử dụng useWindowSize
sẽ tự động cập nhật khi kích thước cửa sổ thay đổi.
Các ví dụ phức tạp hơn
Hãy khám phá một số trường hợp sử dụng nâng cao hơn cho các custom hooks.
Ví dụ: useLocalStorage
Hook này cho phép bạn dễ dàng lưu trữ và truy xuất dữ liệu từ local storage.
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
// State to store our value
// Pass initial value to useState so logic is only executed once
const [storedValue, setStoredValue] = useState(() => {
try {
// Get from local storage by key
const item = window.localStorage.getItem(key);
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// If error also return initialValue
console.log(error);
return initialValue;
}
});
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = (value) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore = value instanceof Function ? value(storedValue) : value;
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore));
// Save state
setStoredValue(valueToStore);
} catch (error) {
// A more advanced implementation would handle the error case
console.log(error);
}
};
useEffect(() => {
try {
const item = window.localStorage.getItem(key);
setStoredValue(item ? JSON.parse(item) : initialValue);
} catch (error) {
console.log(error);
}
}, [key, initialValue]);
return [storedValue, setValue];
}
export default useLocalStorage;
Cách sử dụng:
import React from 'react';
import useLocalStorage from './useLocalStorage';
function MyComponent() {
const [name, setName] = useLocalStorage('name', 'Guest');
return (
Hello, {name}!
setName(e.target.value)}
/>
);
}
export default MyComponent;
Ví dụ: useFetch
Hook này đóng gói logic để tìm nạp dữ liệu từ một API.
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const json = await response.json();
setData(json);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
}
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;
Cách sử dụng:
import React from 'react';
import useFetch from './useFetch';
function MyComponent() {
const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/todos/1');
if (loading) return Loading...
;
if (error) return Error: {error.message}
;
return (
Title: {data.title}
Completed: {data.completed ? 'Yes' : 'No'}
);
}
export default MyComponent;
Các phương pháp hay nhất cho Custom Hooks
Để đảm bảo rằng các custom hooks của bạn hiệu quả và có thể bảo trì, hãy tuân theo các phương pháp hay nhất sau:
- Giữ cho chúng tập trung: Mỗi custom hook nên có một mục đích duy nhất, được xác định rõ ràng. Tránh tạo các hook quá phức tạp cố gắng làm quá nhiều việc.
- Tài liệu hóa các Hook của bạn: Cung cấp tài liệu rõ ràng và ngắn gọn cho mỗi custom hook, giải thích mục đích, đầu vào và đầu ra của nó.
- Kiểm thử các Hook của bạn: Viết các bài kiểm thử đơn vị cho các custom hooks của bạn để đảm bảo chúng hoạt động chính xác và đáng tin cậy.
- Sử dụng tên mô tả: Chọn tên mô tả cho các custom hooks của bạn để chỉ rõ mục đích của chúng.
- Xử lý lỗi một cách duyên dáng: Triển khai xử lý lỗi trong các custom hooks của bạn để ngăn chặn hành vi không mong muốn và cung cấp thông báo lỗi đầy đủ thông tin.
- Cân nhắc khả năng tái sử dụng: Thiết kế các custom hooks của bạn với ý tưởng về khả năng tái sử dụng. Làm cho chúng đủ chung chung để có thể được sử dụng trong nhiều thành phần.
- Tránh trừu tượng hóa quá mức: Đừng tạo custom hooks cho logic đơn giản có thể dễ dàng xử lý trong một thành phần. Chỉ trích xuất logic thực sự có thể tái sử dụng và phức tạp.
Những cạm bẫy phổ biến cần tránh
- Vi phạm các quy tắc của Hooks: Luôn gọi các hook ở cấp cao nhất của hàm custom hook của bạn và chỉ gọi chúng từ các thành phần chức năng React hoặc các custom hooks khác.
- Bỏ qua các phụ thuộc trong useEffect: Đảm bảo bao gồm tất cả các phụ thuộc cần thiết trong mảng phụ thuộc của hook
useEffect
để ngăn chặn các stale closures và hành vi không mong muốn. - Tạo vòng lặp vô hạn: Cẩn thận khi cập nhật trạng thái trong một hook
useEffect
, vì điều này có thể dễ dàng dẫn đến các vòng lặp vô hạn. Đảm bảo rằng việc cập nhật là có điều kiện và dựa trên các thay đổi trong các phụ thuộc. - Quên dọn dẹp: Luôn bao gồm một hàm dọn dẹp trong
useEffect
để xóa các trình lắng nghe sự kiện, hủy đăng ký và thực hiện các tác vụ dọn dẹp khác để ngăn chặn rò rỉ bộ nhớ.
Các mẫu nâng cao
Kết hợp các Custom Hooks
Các custom hooks có thể được kết hợp với nhau để tạo ra logic phức tạp hơn. Ví dụ, bạn có thể kết hợp một hook useLocalStorage
với một hook useFetch
để tự động lưu dữ liệu đã tìm nạp vào local storage.
Chia sẻ Logic giữa các Hook
Nếu nhiều custom hooks chia sẻ logic chung, bạn có thể trích xuất logic đó vào một hàm tiện ích riêng và tái sử dụng nó trong cả hai hook.
Sử dụng Context với Custom Hooks
Custom hooks có thể được sử dụng cùng với React Context để truy cập và cập nhật trạng thái toàn cục. Điều này cho phép bạn tạo các thành phần có thể tái sử dụng nhận biết và có thể tương tác với trạng thái toàn cục của ứng dụng.
Các ví dụ trong thực tế
Dưới đây là một số ví dụ về cách các custom hooks có thể được sử dụng trong các ứng dụng thực tế:
- Xác thực biểu mẫu: Tạo một hook
useForm
để xử lý trạng thái biểu mẫu, xác thực và gửi. - Xác thực người dùng: Triển khai một hook
useAuth
để quản lý xác thực và ủy quyền người dùng. - Quản lý giao diện: Phát triển một hook
useTheme
để chuyển đổi giữa các giao diện khác nhau (sáng, tối, v.v.). - Định vị địa lý: Xây dựng một hook
useGeolocation
để theo dõi vị trí hiện tại của người dùng. - Phát hiện cuộn trang: Tạo một hook
useScroll
để phát hiện khi người dùng đã cuộn đến một điểm nhất định trên trang.
Ví dụ : hook useGeolocation cho các ứng dụng đa văn hóa như bản đồ hoặc dịch vụ giao hàng
import { useState, useEffect } from 'react';
function useGeolocation() {
const [location, setLocation] = useState({
latitude: null,
longitude: null,
error: null,
});
useEffect(() => {
if (!navigator.geolocation) {
setLocation({
latitude: null,
longitude: null,
error: 'Geolocation is not supported by this browser.',
});
return;
}
const watchId = navigator.geolocation.watchPosition(
(position) => {
setLocation({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
error: null,
});
},
(error) => {
setLocation({
latitude: null,
longitude: null,
error: error.message,
});
}
);
return () => navigator.geolocation.clearWatch(watchId);
}, []);
return location;
}
export default useGeolocation;
Kết luận
Custom hooks là một công cụ mạnh mẽ để viết mã React sạch hơn, có thể tái sử dụng hơn và dễ bảo trì hơn. Bằng cách đóng gói logic phức tạp trong các custom hooks, bạn có thể đơn giản hóa các thành phần của mình, giảm sự trùng lặp mã và cải thiện cấu trúc tổng thể của các ứng dụng của bạn. Hãy nắm bắt custom hooks và khai phá tiềm năng của chúng để xây dựng các ứng dụng React mạnh mẽ và có khả năng mở rộng hơn.
Bắt đầu bằng cách xác định các khu vực trong codebase hiện tại của bạn nơi logic đang được lặp lại trên nhiều thành phần. Sau đó, tái cấu trúc logic đó thành các custom hooks. Theo thời gian, bạn sẽ xây dựng một thư viện các hook có thể tái sử dụng, giúp tăng tốc quá trình phát triển và cải thiện chất lượng mã của bạn.
Hãy nhớ tuân theo các phương pháp hay nhất, tránh các cạm bẫy phổ biến và khám phá các mẫu nâng cao để tận dụng tối đa custom hooks. Với thực hành và kinh nghiệm, bạn sẽ trở thành một bậc thầy về custom hooks và một nhà phát triển React hiệu quả hơn.