Tiếng Việt

Tìm hiểu cách tận dụng React custom hooks để trích xuất và tái sử dụng logic thành phần, cải thiện khả năng bảo trì, kiểm thử mã và kiến trúc ứng dụng tổng thể.

React Custom Hooks: Trích xuất logic thành phần để tái sử dụng

React hooks đã cách mạng hóa cách chúng ta viết các thành phần React, mang lại một cách tiếp cận thanh lịch và hiệu quả hơn để quản lý trạng thái và các tác dụng phụ. Trong số các hook khác nhau có sẵn, custom hooks nổi bật như một công cụ mạnh mẽ để trích xuất và tái sử dụng logic thành phần. Bài viết này cung cấp một hướng dẫn toàn diện để hiểu và triển khai React custom hooks, giúp bạn xây dựng các ứng dụng dễ bảo trì, kiểm thử và mở rộng hơn.

React Custom Hooks là gì?

Về bản chất, một custom hook là một hàm JavaScript có tên bắt đầu bằng "use" và có thể gọi các hook khác. Nó cho phép bạn trích xuất logic thành phần vào các hàm có thể tái sử dụng, từ đó loại bỏ sự trùng lặp mã và thúc đẩy cấu trúc thành phần sạch hơn. Không giống như các thành phần React thông thường, custom hooks không hiển thị bất kỳ giao diện người dùng nào; chúng chỉ đơn thuần đóng gói logic.

Hãy nghĩ chúng là các hàm có thể tái sử dụng có thể truy cập trạng thái React và các tính năng vòng đời. Chúng là một cách tuyệt vời để chia sẻ logic có trạng thái giữa các thành phần khác nhau mà không cần dùng đến các higher-order components (HOCs) hoặc render props, những thứ thường dẫn đến mã khó đọc và bảo trì.

Tại sao nên sử dụng Custom Hooks?

Lợi ích của việc sử dụng custom hooks rất nhiều:

Tạo Custom Hook đầu tiên của bạn

Hãy minh họa việc tạo và sử dụng một custom hook với một ví dụ thực tế: tìm nạp dữ liệu từ một API.

Ví dụ: useFetch - Một Hook tìm nạp dữ liệu

Hãy tưởng tượng bạn thường xuyên cần tìm nạp dữ liệu từ các API khác nhau trong ứng dụng React của mình. Thay vì lặp lại logic tìm nạp trong mỗi thành phần, bạn có thể tạo một 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); // Clear any previous errors
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('Fetch aborted');
        } else {
          setError(error);
        }
        setData(null); // Clear any previous data
      } finally {
        setLoading(false);
      }
    };

    fetchData();

    return () => {
      abortController.abort(); // Cleanup function to abort the fetch on unmount or URL change
    };
  }, [url]); // Re-run effect when the URL changes

  return { data, loading, error };
}

export default useFetch;

Giải thích:

Sử dụng Hook useFetch trong một Thành phần

Bây giờ, hãy xem cách sử dụng custom hook này trong một thành phần React:


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>Đang tải người dùng...</p>;
  if (error) return <p>Lỗi: {error.message}</p>;
  if (!users) return <p>Không tìm thấy người dùng nào.</p>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name} ({user.email})</li>
      ))}
    </ul>
  );
}

export default UserList;

Giải thích:

Các mẫu Custom Hook nâng cao

Ngoài việc tìm nạp dữ liệu đơn giản, custom hooks có thể được sử dụng để đóng gói logic phức tạp hơn. Dưới đây là một vài mẫu nâng cao:

1. Quản lý trạng thái với useReducer

Đối với các tình huống quản lý trạng thái phức tạp hơn, bạn có thể kết hợp custom hooks với useReducer. Điều này cho phép bạn quản lý các chuyển đổi trạng thái một cách dễ đoán và có tổ chức hơn.


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;

Cách sử dụng:


import React from 'react';
import useCounter from './useCounter';

function Counter() {
  const { count, increment, decrement } = useCounter();

  return (
    <div>
      <p>Số đếm: {count}</p>
      <button onClick={increment}>Tăng</button>
      <button onClick={decrement}>Giảm</button>
    </div>
  );
}

export default Counter;

2. Tích hợp Context với useContext

Custom hooks cũng có thể được sử dụng để đơn giản hóa việc truy cập React Context. Thay vì sử dụng useContext trực tiếp trong các thành phần của bạn, bạn có thể tạo một custom hook đóng gói logic truy cập context.


import { useContext } from 'react';
import { ThemeContext } from './ThemeContext'; // Giả sử bạn có ThemeContext

function useTheme() {
  return useContext(ThemeContext);
}

export default useTheme;

Cách sử dụng:


import React from 'react';
import useTheme from './useTheme';

function MyComponent() {
  const { theme, toggleTheme } = useTheme();

  return (
    <div style={{ backgroundColor: theme.background, color: theme.color }}>
      <p>Đây là thành phần của tôi.</p>
      <button onClick={toggleTheme}>Chuyển đổi chủ đề</button>
    </div>
  );
}

export default MyComponent;

3. Debouncing và Throttling

Debouncing và throttling là các kỹ thuật được sử dụng để kiểm soát tốc độ thực thi một hàm. Custom hooks có thể được sử dụng để đóng gói logic này, giúp dễ dàng áp dụng các kỹ thuật này cho các trình xử lý sự kiện.


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;

Cách sử dụng:


import React, { useState } from 'react';
import useDebounce from './useDebounce';

function SearchInput() {
  const [searchValue, setSearchValue] = useState('');
  const debouncedSearchValue = useDebounce(searchValue, 500); // Debounce trong 500ms

  useEffect(() => {
    // Thực hiện tìm kiếm với debouncedSearchValue
    console.log('Đang tìm kiếm:', debouncedSearchValue);
    // Thay thế console.log bằng logic tìm kiếm thực tế của bạn
  }, [debouncedSearchValue]);

  const handleChange = (event) => {
    setSearchValue(event.target.value);
  };

  return (
    <input
      type="text"
      value={searchValue}
      onChange={handleChange}
      placeholder="Tìm kiếm..."
    />
  );
}

export default SearchInput;

Thực tiễn tốt nhất để viết Custom Hooks

Để đảm bảo custom hooks của bạn hiệu quả và dễ bảo trì, hãy làm theo các thực tiễn tốt nhất sau:

Những cân nhắc toàn cầu

Khi phát triển ứng dụng cho đối tượng toàn cầu, hãy ghi nhớ những điều sau:

Ví dụ: Định dạng Ngày Quốc tế hóa với 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('Lỗi định dạng ngày:', error);
      setFormattedDate('Ngày không hợp lệ');
    }
  }, [date, locale]);

  return formattedDate;
}

export default useFormattedDate;

Cách sử dụng:


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>Ngày US: {enDate}</p>
      <p>Ngày Pháp: {frDate}</p>
      <p>Ngày Đức: {deDate}</p>
    </div>
  );
}

export default MyComponent;

Kết luận

React custom hooks là một cơ chế mạnh mẽ để trích xuất và tái sử dụng logic thành phần. Bằng cách tận dụng custom hooks, bạn có thể viết mã sạch hơn, dễ bảo trì và kiểm thử hơn. Khi bạn trở nên thành thạo hơn với React, việc nắm vững custom hooks sẽ cải thiện đáng kể khả năng xây dựng các ứng dụng phức tạp và có thể mở rộng của bạn. Hãy nhớ tuân thủ các thực tiễn tốt nhất và xem xét các yếu tố toàn cầu khi phát triển custom hooks để đảm bảo chúng hiệu quả và dễ tiếp cận cho nhiều đối tượng khác nhau. Hãy nắm bắt sức mạnh của custom hooks và nâng cao kỹ năng phát triển React của bạn!