Tiếng Việt

Tìm hiểu cách tính năng gộp lệnh tự động của React tối ưu hóa nhiều lần cập nhật state, cải thiện hiệu suất ứng dụng và ngăn chặn các lần re-render không cần thiết. Khám phá các ví dụ và phương pháp hay nhất.

React Automatic Batching: Tối ưu hóa cập nhật State để tăng hiệu suất

Hiệu suất của React là yếu tố then chốt để tạo ra giao diện người dùng mượt mà và phản hồi nhanh. Một trong những tính năng quan trọng được giới thiệu để cải thiện hiệu suất là gộp lệnh tự động (automatic batching). Kỹ thuật tối ưu hóa này tự động nhóm nhiều lần cập nhật state vào một lần re-render duy nhất, mang lại lợi ích hiệu suất đáng kể. Điều này đặc biệt phù hợp trong các ứng dụng phức tạp có sự thay đổi state thường xuyên.

React Automatic Batching là gì?

Batching, trong ngữ cảnh của React, là quá trình nhóm nhiều lần cập nhật state thành một bản cập nhật duy nhất. Trước React 18, việc gộp lệnh chỉ được áp dụng cho các cập nhật xảy ra bên trong các trình xử lý sự kiện (event handlers) của React. Các cập nhật bên ngoài trình xử lý sự kiện, chẳng hạn như trong setTimeout, promises, hoặc các trình xử lý sự kiện gốc (native event handlers), không được gộp lại. Điều này có thể dẫn đến các lần re-render không cần thiết và gây tắc nghẽn hiệu suất.

React 18 đã giới thiệu gộp lệnh tự động, mở rộng sự tối ưu hóa này cho tất cả các cập nhật state, bất kể chúng xảy ra ở đâu. Điều này có nghĩa là dù cập nhật state của bạn diễn ra bên trong một trình xử lý sự kiện React, một callback của setTimeout, hay một promise đã được giải quyết, React sẽ tự động gộp chúng lại với nhau thành một lần re-render duy nhất.

Tại sao Automatic Batching lại quan trọng?

Gộp lệnh tự động mang lại một số lợi ích chính:

Cách Automatic Batching hoạt động

React thực hiện gộp lệnh tự động bằng cách trì hoãn việc thực thi các cập nhật state cho đến cuối bối cảnh thực thi hiện tại. Điều này cho phép React thu thập tất cả các cập nhật state đã xảy ra trong bối cảnh đó và gộp chúng lại thành một bản cập nhật duy nhất.

Hãy xem xét ví dụ đơn giản sau:

function ExampleComponent() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  function handleClick() {
    setTimeout(() => {
      setCount1(count1 + 1);
      setCount2(count2 + 1);
    }, 0);
  }

  return (
    <div>
      <p>Count 1: {count1}</p>
      <p>Count 2: {count2}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

Trước React 18, việc nhấp vào nút sẽ kích hoạt hai lần re-render: một cho setCount1 và một lần khác cho setCount2. Với tính năng gộp lệnh tự động trong React 18, cả hai cập nhật state đều được gộp lại, chỉ dẫn đến một lần re-render duy nhất.

Ví dụ về Automatic Batching trong thực tế

1. Cập nhật bất đồng bộ

Các hoạt động bất đồng bộ, chẳng hạn như tìm nạp dữ liệu từ một API, thường liên quan đến việc cập nhật state sau khi hoạt động hoàn tất. Gộp lệnh tự động đảm bảo rằng các cập nhật state này được gộp lại với nhau, ngay cả khi chúng xảy ra trong callback bất đồng bộ.

function DataFetchingComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch('https://api.example.com/data');
        const jsonData = await response.json();
        setData(jsonData);
        setLoading(false);
      } catch (error) {
        console.error('Error fetching data:', error);
        setLoading(false);
      }
    }

    fetchData();
  }, []);

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

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

Trong ví dụ này, setDatasetLoading đều được gọi bên trong hàm bất đồng bộ fetchData. React sẽ gộp các cập nhật này lại với nhau, dẫn đến một lần re-render duy nhất sau khi dữ liệu được tìm nạp và trạng thái tải được cập nhật.

2. Promises

Tương tự như các cập nhật bất đồng bộ, promises thường liên quan đến việc cập nhật state khi promise được giải quyết (resolve) hoặc bị từ chối (reject). Gộp lệnh tự động đảm bảo rằng các cập nhật state này cũng được gộp lại với nhau.

function PromiseComponent() {
  const [result, setResult] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    const myPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        const success = Math.random() > 0.5;
        if (success) {
          resolve('Promise resolved!');
        } else {
          reject('Promise rejected!');
        }
      }, 1000);
    });

    myPromise
      .then((value) => {
        setResult(value);
        setError(null);
      })
      .catch((err) => {
        setError(err);
        setResult(null);
      });
  }, []);

  if (error) {
    return <p>Error: {error}</p>;
  }

  if (result) {
    return <p>Result: {result}</p>;
  }

  return <p>Loading...</p>;
}

Trong trường hợp này, hoặc là setResultsetError(null) được gọi khi thành công, hoặc setErrorsetResult(null) được gọi khi thất bại. Dù thế nào đi nữa, gộp lệnh tự động sẽ kết hợp chúng thành một lần re-render duy nhất.

3. Trình xử lý sự kiện gốc (Native Event Handlers)

Đôi khi, bạn có thể cần sử dụng các trình xử lý sự kiện gốc (ví dụ: addEventListener) thay vì các trình xử lý sự kiện tổng hợp của React. Gộp lệnh tự động cũng hoạt động trong những trường hợp này.

function NativeEventHandlerComponent() {
  const [scrollPosition, setScrollPosition] = useState(0);

  useEffect(() => {
    function handleScroll() {
      setScrollPosition(window.scrollY);
    }

    window.addEventListener('scroll', handleScroll);

    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, []);

  return <p>Scroll Position: {scrollPosition}</p>;
}

Mặc dù setScrollPosition được gọi bên trong một trình xử lý sự kiện gốc, React vẫn sẽ gộp các cập nhật lại với nhau, ngăn chặn việc re-render quá mức khi người dùng cuộn trang.

Tùy chọn không sử dụng Automatic Batching

Trong một số trường hợp hiếm hoi, bạn có thể muốn từ chối việc gộp lệnh tự động. Ví dụ, bạn có thể muốn buộc một cập nhật đồng bộ để đảm bảo rằng giao diện người dùng được cập nhật ngay lập tức. React cung cấp API flushSync cho mục đích này.

Lưu ý: Việc sử dụng flushSync nên được thực hiện một cách hạn chế, vì nó có thể ảnh hưởng tiêu cực đến hiệu suất. Nhìn chung, tốt nhất là nên dựa vào việc gộp lệnh tự động bất cứ khi nào có thể.

import { flushSync } from 'react-dom';

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

  function handleClick() {
    flushSync(() => {
      setCount(count + 1);
    });
  }

  return (<button onClick={handleClick}>Increment</button>);
}

Trong ví dụ này, flushSync buộc React phải cập nhật state và re-render component ngay lập tức, bỏ qua việc gộp lệnh tự động.

Các phương pháp tốt nhất để tối ưu hóa cập nhật State

Mặc dù gộp lệnh tự động mang lại những cải tiến hiệu suất đáng kể, việc tuân thủ các phương pháp tốt nhất để tối ưu hóa cập nhật state vẫn rất quan trọng:

Automatic Batching và những cân nhắc toàn cầu

Gộp lệnh tự động, là một tối ưu hóa hiệu suất cốt lõi của React, mang lại lợi ích cho các ứng dụng trên toàn cầu bất kể vị trí, tốc độ mạng hay thiết bị của người dùng. Tuy nhiên, tác động của nó có thể rõ rệt hơn trong các kịch bản có kết nối internet chậm hơn hoặc các thiết bị kém mạnh mẽ hơn. Đối với khán giả quốc tế, hãy xem xét các điểm sau:

Kết luận

Gộp lệnh tự động trong React là một kỹ thuật tối ưu hóa mạnh mẽ có thể cải thiện đáng kể hiệu suất của các ứng dụng React của bạn. Bằng cách tự động nhóm nhiều lần cập nhật state vào một lần re-render duy nhất, nó giúp giảm chi phí render, ngăn chặn các trạng thái không nhất quán và mang lại trải nghiệm người dùng mượt mà, phản hồi nhanh hơn. Bằng cách hiểu cách hoạt động của gộp lệnh tự động và tuân thủ các phương pháp tốt nhất để tối ưu hóa cập nhật state, bạn có thể xây dựng các ứng dụng React hiệu suất cao mang lại trải nghiệm tuyệt vời cho người dùng trên toàn thế giới. Tận dụng các công cụ như React DevTools giúp tinh chỉnh và tối ưu hóa hơn nữa hồ sơ hiệu suất của ứng dụng trong các môi trường toàn cầu đa dạng.