Tất tần tật về React Hook dành cho người mới

Tất tần tật về React Hook dành cho người mới

Giới thiệu

Trong bài viết này, chúng ta sẽ tìm hiểu về React Hook và Custom hook, hai công cụ mạnh mẽ giúp chúng ta xây dựng ứng dụng React một cách hiệu quả và linh hoạt.

React Hook

React Hook là một tính năng mới trong React 16.8 giúp bạn sử dụng state và các tính năng khác của React mà không cần tạo một class. Dưới đây là một số loại Hook phổ biến và cách sử dụng chúng.

useState

useState là một Hook cơ bản trong React, giúp thêm state vào trong functional components. Trước khi có Hooks, việc sử dụng state chỉ có thể thực hiện trong class components. Nhưng với useState, bạn có thể sử dụng state một cách dễ dàng ngay trong functional components.

useState nhận vào một giá trị khởi tạo và trả về một mảng gồm hai phần tử. Phần tử đầu tiên là giá trị hiện tại của state, phần tử thứ hai là một hàm giúp cập nhật giá trị state này.

Ví dụ về việc sử dụng useState:

 const [count, setCount] = useState(0);

Trong đoạn mã trên, count là giá trị hiện tại của state, ban đầu được khởi tạo là 0. setCount là hàm dùng để cập nhật giá trị của count. Khi bạn muốn thay đổi giá trị của count, bạn chỉ cần gọi hàm setCount với giá trị mới:

setCount(count + 1);

Mỗi lần gọi hàm setCount, React sẽ re-render component với giá trị mới của count.

useState rất hữu ích và là nền tảng cơ bản cho việc sử dụng React Hooks. Việc hiểu rõ cách hoạt động của useState sẽ giúp bạn tận dụng tốt hơn sức mạnh của React Hooks.

useEffect

useEffect là một Hook trong React giúp bạn thực hiện các "side effect" trong functional components. Side effect ở đây có thể hiểu là các hành động mà không phải chỉ liên quan đến render của component, như gọi API, thực hiện các tác vụ liên quan đến DOM và hơn thế nữa.

Khi bạn muốn thực hiện một side effect sau mỗi lần render, bạn có thể sử dụng useEffect. Ví dụ:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]);

Trong ví dụ trên, useEffect dùng để thay đổi title của document sau khi số lần click (count) thay đổi.

useEffect nhận vào hai tham số, tham số đầu tiên là một hàm chứa side effect bạn muốn thực hiện, tham số thứ hai là một array các dependencies. Khi một trong các dependencies thay đổi, side effect sẽ được thực hiện sau lần render tiếp theo.

Nếu bạn không truyền vào array dependencies (tham số thứ hai), side effect sẽ được thực hiện sau mỗi lần render.

Nếu bạn truyền vào một array rỗng [], side effect sẽ chỉ được thực hiện sau lần render đầu tiên (tương tự như componentDidMount trong class components).

Cần lưu ý là useEffect làm việc sau khi render, nghĩa là sau khi giao diện người dùng đã được cập nhật. Điều này giúp chúng ta tránh được việc block UI trong quá trình render.

useRef

useRef là một hook trong React giúp bạn tạo ra một tham chiếu đến một phần tử DOM. Tham chiếu này có thể được sử dụng để truy cập và thao tác trực tiếp với phần tử DOM đó.

Ví dụ, bạn có thể sử dụng useRef để tạo ra một tham chiếu đến một input field, và sau đó sử dụng tham chiếu này để truy cập vào giá trị của input field đó.

const inputRef = useRef(null);

function handleButtonClick() {
  // Truy cập vào giá trị của input field qua inputRef
  console.log(inputRef.current.value);
}

return (
  <div>
    <input ref={inputRef} type="text" />
    <button onClick={handleButtonClick}>Show input value</button>
  </div>
);

Trong ví dụ trên, inputRef được truyền vào thuộc tính ref của phần tử input. Khi button được nhấn, hàm handleButtonClick sẽ được gọi và giá trị của input field sẽ được in ra console.

Lưu ý rằng useRef không chỉ sử dụng để tạo tham chiếu đến DOM. Bạn cũng có thể sử dụng nó để lưu trữ bất kỳ giá trị nào mà bạn muốn giữ nguyên qua các lần render của component. Khác với useState, việc thay đổi giá trị của ref không làm component re-render.

useImperativeHandle

useImperativeHandle là một Hook trong React giúp bạn tạo ra một giá trị instance tùy chỉnh. Nó thường được sử dụng cùng với forwardRef để cho phép bạn truy cập vào các phương thức hoặc thuộc tính tuỳ chỉnh của một component con từ component cha.

Ví dụ:

const FancyInput = React.forwardRef((props, ref) => {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} />;
});

// Sử dụng FancyInput
const ref = useRef();
<FancyInput ref={ref} />
// Sử dụng ref để truy cập vào phương thức focus
ref.current.focus();

Trong ví dụ trên, useImperativeHandle được sử dụng để tạo ra một phương thức focus có thể được gọi từ component cha qua ref. inputRef là một ref được tạo ra bằng useRef, và nó được liên kết với input element trong JSX. Khi phương thức focus được gọi, nó sẽ gọi phương thức focus của element input.

Tuy nhiên, useImperativeHandle nên được sử dụng một cách hạn chế, vì việc sử dụng nó quá mức có thể làm giảm tính đơn giản và dễ hiểu của code React. Nó chỉ nên được sử dụng khi không có cách nào khác để đạt được mục tiêu của bạn.

useCallback

useCallback là một Hook trong React giúp tạo ra phiên bản được memoized (lưu trữ) của một hàm. Điều này có nghĩa là hàm đó sẽ không được thực thi lại nếu như các phụ thuộc của nó không thay đổi, giúp tiết kiệm tài nguyên thời gian thực thi.

Đây là cú pháp của useCallback:

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

Trong đó, doSomething(a, b) là hàm cần được memoize. [a, b] là mảng các phụ thuộc - nếu như giá trị của a hoặc b thay đổi, hàm doSomething(a, b) sẽ được thực thi lại.

useCallback hữu ích khi bạn có một hàm mà việc thực thi nó tốn nhiều tài nguyên và hàm đó được sử dụng trong quá trình render (ví dụ, được truyền vào một component con như props). Việc sử dụng useCallback giúp đảm bảo rằng hàm chỉ được thực thi lại khi cần thiết, giúp cải thiện hiệu suất render. Tuy nhiên, cần lưu ý sử dụng useCallback một cách hợp lý, vì việc tạo ra một hàm memoized cũng tốn một chút tài nguyên.

useMemo

useMemo là một Hook trong React giúp bạn tạo ra một phiên bản memoized của một giá trị. Điều này có nghĩa là, nếu các tham số đầu vào không thay đổi, giá trị sẽ không được tính toán lại mà sẽ được lấy từ bộ nhớ cache.

Đây là một ví dụ sử dụng useMemo:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

 

Trong ví dụ này, computeExpensiveValue(a, b) là một hàm mà việc tính toán giá trị của nó có thể tốn kém về mặt tài nguyên hoặc thời gian. useMemo được sử dụng để đảm bảo rằng hàm này chỉ được gọi khi giá trị của a hoặc b thay đổi. Nếu không, giá trị được lấy từ bộ nhớ cache.

useMemo rất hữu ích khi bạn có các tính toán phức tạp mà bạn không muốn thực hiện lại mỗi khi component render. Tuy nhiên, bạn cần cẩn thận khi sử dụng useMemo vì việc lưu giá trị vào bộ nhớ cache cũng tốn tài nguyên. Do đó, chỉ sử dụng useMemo khi cần thiết.

useContext

useContext là một Hook trong React cho phép bạn truy cập trực tiếp vào giá trị của Context mà không cần phải sử dụng Consumer hoặc Context.Consumer. Điều này giúp cho việc truy cập vào dữ liệu từ Context trở nên đơn giản hơn rất nhiều.

Khi sử dụng useContext, bạn chỉ cần truyền vào Context mà bạn muốn sử dụng, và nó sẽ trả về giá trị hiện tại của Context đó. Đây là một cách tiện lợi để truy cập vào dữ liệu mà không cần phải đi qua nhiều lớp của component tree.

import React, { useContext } from 'react';

const MyContext = React.createContext();

function MyComponent() {
  const contextValue = useContext(MyContext);
  //...
}

Trong đoạn code trên, MyComponent sẽ có quyền truy cập trực tiếp vào giá trị của MyContext mà không cần phải sử dụng Consumer hoặc Context.Consumer. Điều này giúp cho việc đọc và cập nhật dữ liệu từ Context trở nên dễ dàng hơn.

Hơn nữa, một điểm mạnh của useContext là nó sẽ tự động cập nhật giá trị khi Context thay đổi. Điều này giúp cho việc quản lý state trở nên dễ dàng hơn. Bạn không cần phải thực hiện bất kỳ công việc nào để đảm bảo rằng component của bạn đang sử dụng giá trị Context mới nhất.

Tuy nhiên, cần lưu ý rằng việc sử dụng useContext không thay thế được HOC (Higher Order Component) hoặc render props khi muốn chia sẻ logic giữa các component. Trong một số trường hợp, việc sử dụng HOC hoặc render props có thể mang lại hiệu suất tốt hơn hoặc giúp mã nguồn dễ đọc hơn.

Nói chung, useContext là một công cụ mạnh mẽ giúp bạn làm việc với Context một cách dễ dàng và hiệu quả hơn. Nó không chỉ giúp giải quyết những vấn đề liên quan đến việc truy cập dữ liệu từ nhiều lớp của component tree, mà còn giúp code của bạn trở nên gọn gàng và dễ đọc hơn.

Custom Hook

Custom Hook là một khái niệm mạnh mẽ trong React, cho phép bạn tạo ra các logic có thể tái sử dụng trong nhiều component khác nhau mà không làm phức tạp code. Một Custom Hook không phải là một tính năng React cụ thể, mà là một mô hình thiết kế mà bạn có thể sử dụng để tối ưu code của mình.

Để tạo một Custom Hook, bạn bắt đầu bằng việc viết một hàm thông thường. Quy ước là tên hàm bắt đầu với "use", giúp bạn nhận ra rằng đây là một hook. Trong hàm này, bạn có thể sử dụng bất kỳ hook nào bạn muốn. Ví dụ, bạn có thể sử dụng useStateuseEffect để quản lý trạng thái nội bộ và thực hiện các hành động liên quan đến hiệu ứng.

Thông thường, một Custom Hook sẽ trả về một giá trị hoặc một hàm, giúp component sử dụng hook có thể tương tác với nó. Ví dụ, một Custom Hook có thể trả về một giá trị trạng thái và một hàm để cập nhật trạng thái đó.

Một điểm mạnh của Custom Hook là nó giúp tách biệt logic từ component, giúp component trở nên gọn gàng và dễ đọc hơn. Ngoài ra, vì Custom Hook là các hàm JavaScript thông thường, bạn có thể tạo ra các unit test cho chúng, giúp đảm bảo chúng hoạt động đúng như mong đợi.

Tóm lại, Custom Hook là một công cụ mạnh mẽ giúp bạn tối ưu code, tăng tính tái sử dụng và giữ cho component của bạn gọn gàng và dễ duy trì.

Fetch API với Custom Hook

Trong ví dụ dưới đây, chúng ta sẽ tạo ra một custom hook useFetch để thực hiện việc tải dữ liệu từ API:

function useFetch(url) {
  // Khởi tạo state cho dữ liệu và trạng thái tải
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  // Sử dụng useEffect để thực hiện việc tải dữ liệu khi component được render
  useEffect(() => {
    const fetchData = async () => {
      // Gọi API
      const response = await fetch(url);
      // Chuyển đổi dữ liệu trả về thành JSON
      const data = await response.json();
      // Cập nhật state
      setData(data);
      // Đánh dấu việc tải dữ liệu đã hoàn tất
      setLoading(false);
    };
    // Thực thi hàm fetchData
    fetchData();
  }, [url]); // Mảng dependencies chứa url, nghĩa là khi url thay đổi, useEffect sẽ chạy lại

  // Trả về dữ liệu và trạng thái tải
  return { data, loading };
}

Custom hook useFetch này có thể được sử dụng trong bất kỳ component nào cần tải dữ liệu từ API. Chỉ cần gọi useFetch với url của API là có thể lấy được dữ liệu và trạng thái tải:

function Component() {
  const { data, loading } = useFetch('<https://api.example.com/data>');

  if (loading) {
    return <div>Loading...</div>;
  }

  return <div>Data: {JSON.stringify(data)}</div>;
}

Trong đoạn mã trên, chúng ta sử dụng useFetch trong một component để tải dữ liệu từ https://api.example.com/data Trạng thái tải và dữ liệu được trả về từ useFetch được sử dụng để hiển thị UI tương ứng.

 

Cập nhật Layout với Custom Hook

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return width;
}

Ví dụ sau đây mô tả cách cập nhật layout của ứng dụng React sử dụng custom hook useWindowWidth:

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

function MyComponent() {
  const width = useWindowWidth();

  return (
    <div>
      Chiều rộng cửa sổ hiện tại là: {width}
    </div>
  );
}

Trong ví dụ trên, chúng ta sử dụng custom hook useWindowWidth trong component MyComponent. Hook này trả về chiều rộng hiện tại của cửa sổ trình duyệt.

Khi kích thước cửa sổ thay đổi, useWindowWidth cập nhật giá trị width và component MyComponent sẽ được re-render với chiều rộng mới. Điều này cho phép chúng ta cập nhật layout của ứng dụng để phản ánh thay đổi trong kích thước cửa sổ.

Lưu ý rằng useWindowWidth cũng loại bỏ listener khi component unmount, ngăn chặn memory leaks liên quan đến listeners không cần thiết. Điều này là một ví dụ về cách custom hooks có thể giúp chúng ta tổ chức code của mình một cách hiệu quả và tái sử dụng logic trạng thái và side effects.

Hy vọng với bài viết này, bạn đã hiểu rõ hơn về React Hook và Custom hook và cách ứng dụng chúng vào dự án của mình. Chúng tôi rất mong nhận được phản hồi từ bạn.