0
0
Lập trình
NM

Hướng dẫn chi tiết tối ưu hóa vấn đề re-render trong Context API của React

Đăng vào 7 tháng trước

• 7 phút đọc

Giới thiệu

Trong quá trình phát triển ứng dụng với ReactJS, chúng ta thường sử dụng Context API để quản lý trạng thái toàn cục. Tuy nhiên, một trong những vấn đề phổ biến mà các nhà phát triển gặp phải là khi giá trị của context thay đổi, tất cả các thành phần tiêu thụ context đó sẽ bị re-render, bất kể chúng có sử dụng phần cụ thể nào của context đã thay đổi hay không. Điều này dẫn đến các vấn đề về hiệu suất, đặc biệt là trong các ứng dụng lớn với cấu trúc component phức tạp.

Nguyên nhân gây ra vấn đề re-render

Context API của React không tự động biết hoặc theo dõi phần nào hoặc thuộc tính nào (như state) mà một component sử dụng. React chỉ biết rằng một component sử dụng một số phần hoặc thuộc tính của context. Do đó, khi giá trị của context thay đổi, React thông báo cho tất cả các consumer hoặc tất cả các component tiêu thụ các thuộc tính context đó, dẫn đến việc tất cả các consumer đều bị re-render.

Cách thức hoạt động

Khi một giá trị context thay đổi, các bước sau sẽ xảy ra:

  1. React lên lịch cho việc re-render tất cả các component:
    • Sử dụng hook useContext(Context)
    • Được bao bọc trong
    • Là phần tử con của một component sử dụng context

Ví dụ thực tế

Chúng ta sẽ minh họa vấn đề này thông qua một đoạn mã. Hãy sử dụng Vite để tạo một ứng dụng React và đưa đoạn mã dưới đây vào file App.jsx:

javascript Copy
import { createContext, useContext, useState } from "react";

const AppContext = createContext();

function App() {
  const [state, setState] = useState({
    theme: { color: "blue", fontSize: 16 },
    user: { name: "John", id: 1 },
    settings: { notifications: true, language: "en" },
  });

  const changeTheme = () => {
    setState((prev) => ({
      ...prev,
      theme: {
        ...prev.theme,
        color: prev.theme.color === "red" ? "blue" : "red",
      },
    }));
  };

  return (
    <AppContext.Provider value={state}>
      <div>
        <button onClick={changeTheme}>Thay đổi chủ đề</button>

        {/* Tất cả các component này sẽ re-render khi bất kỳ phần nào của context thay đổi */}
        <ThemeDisplay />
        <UserProfile />
        <SettingsPanel />
      </div>
    </AppContext.Provider>
  );
}

function ThemeDisplay() {
  const state = useContext(AppContext);
  console.log("ThemeDisplay re-rendered");

  return (
    <div style={{ color: state.theme.color }}>Chủ đề: {state.theme.color}</div>
  );
}

function UserProfile() {
  const state = useContext(AppContext);
  console.log("UserProfile re-rendered");

  return <div>Người dùng: {state.user.name}</div>;
}

function SettingsPanel() {
  const state = useContext(AppContext);
  console.log("SettingsPanel re-rendered");

  return (
    <div>Thông báo: {state.settings.notifications ? "Bật" : "Tắt"}</div>
  );
}

export default App;

Phân tích mã

Đoạn mã trên cho thấy cách mà Context API của React dẫn đến việc tất cả các consumer hoặc các component sử dụng useContext đều bị re-render mỗi khi giá trị context thay đổi, ngay cả khi chỉ một phần của state được cập nhật. Component App quản lý một đối tượng state toàn cục chứa theme, usersettings và cung cấp nó cho tất cả các component con thông qua AppContext.Provider. Một nút trong component App cho phép thay đổi theme.color, điều này chỉ cập nhật thuộc tính theme trong state. Các component con (ThemeDisplay, UserProfile, và SettingsPanel) đều sử dụng cùng một AppContext thông qua useContext. Tất cả các component con cũng đều có một console.log để in ra thông báo rằng chúng đã bị re-render.

Thực hiện kiểm tra

Sau khi thêm mã vào ứng dụng React của bạn trong App.jsx, hãy khởi động máy chủ localhost và mở ứng dụng React trong một cửa sổ. Mở Công cụ phát triển Chrome trong cùng một cửa sổ mà ứng dụng React đang chạy. Hãy thử nhấp vào nút thay đổi chủ đề. Mở tab console và bạn sẽ thấy tất cả các component con hiển thị thông báo rằng chúng đã được re-render, ngay cả khi chỉ có thuộc tính theme thay đổi.

Giải pháp tối ưu hóa

Để tối ưu hóa và giảm thiểu việc re-render, một trong những cách là biến các component con thành các component độc lập và chỉ truyền những dữ liệu context có liên quan dưới dạng props. Chúng ta cũng cần bao bọc các component con bằng memo từ React. Bằng cách này, bất cứ khi nào context cập nhật, các component con sẽ không re-render miễn là các props mà chúng sử dụng không thay đổi.

Mã tối ưu hóa

Dưới đây là đoạn mã cải tiến theo cách này:

javascript Copy
import { createContext, useState, memo } from "react";

const AppContext = createContext();

function App() {
  const [state, setState] = useState({
    theme: { color: "blue", fontSize: 16 },
    user: { name: "John", id: 1 },
    settings: { notifications: true, language: "en" },
  });

  const changeTheme = () => {
    setState((prev) => ({
      ...prev,
      theme: {
        ...prev.theme,
        color: prev.theme.color === "blue" ? "red" : "blue",
      },
    }));
  };

  return (
    <AppContext.Provider value={state}>
      <div>
        <button onClick={changeTheme}>Thay đổi Chủ đề</button>
        <ThemeContainer theme={state.theme} />
        <UserContainer user={state.user} />
        <SettingsContainer settings={state.settings} />
      </div>
    </AppContext.Provider>
  );
}

const ThemeContainer = memo(({ theme }) => {
  console.log("ThemeContainer re-rendered");
  return <ThemeDisplay color={theme.color} fontSize={theme.fontSize} />;
});

const UserContainer = memo(({ user }) => {
  console.log("UserContainer re-rendered");
  return <UserProfile name={user.name} id={user.id} />;
});

const SettingsContainer = memo(({ settings }) => {
  console.log("SettingsContainer re-rendered");
  return (
    <SettingsPanel
      notifications={settings.notifications}
      language={settings.language}
    />
  );
});

const ThemeDisplay = memo(({ color, fontSize }) => {
  console.log("ThemeDisplay re-rendered");
  return (
    <div style={{ color, fontSize: `${fontSize}px` }}>
      Chủ đề: {color}, Kích thước chữ: {fontSize}px
    </div>
  );
});

const UserProfile = memo(({ name, id }) => {
  console.log("UserProfile re-rendered");
  return (
    <div>
      Người dùng: {name} (ID: {id})
    </div>
  );
});

const SettingsPanel = memo(({ notifications, language }) => {
  console.log("SettingsPanel re-rendered");
  return (
    <div>
      Thông báo: {notifications ? "Bật" : "Tắt"}, Ngôn ngữ: {language}
    </div>
  );
});

export default App;

Phân tích mã tối ưu hóa

Trong đoạn mã trên, chúng ta đã tách các component con thành các component độc lập và sử dụng memo để tối ưu hóa. Bây giờ, khi bạn khởi động máy chủ React localhost và thử nhấp vào các nút Thay đổi Chủ đề, bạn sẽ thấy rằng chỉ các component con liên quan mới được re-render, trong khi các component khác không bị ảnh hưởng.

Tóm tắt và kết luận

Chúng ta đã thực hiện các bước cần thiết để tối ưu hóa vấn đề re-render trong Context API của React bằng cách tách các component độc lập và sử dụng memo. Kết quả là, việc thay đổi chủ đề sẽ không kích hoạt lại UserProfile hay SettingsPanel, và việc thay đổi người dùng sẽ không làm ThemeDisplay hay SettingsPanel bị re-render. Chỉ các component liên quan mới được re-render, giúp cải thiện hiệu suất ứng dụng của bạn.

Các thực tiễn tốt nhất

  • Sử dụng memo cho các component con để tránh re-render không cần thiết.
  • Chỉ truyền các props cần thiết cho các component con.

Những cạm bẫy thường gặp

  • Không tách các component con thành các component độc lập sẽ dẫn đến việc re-render không cần thiết.

Mẹo hiệu suất

  • Theo dõi số lần re-render của các component bằng cách sử dụng console.log trong quá trình phát triển.

Hỏi đáp (FAQ)

1. Tại sao tôi cần sử dụng Context API?
Context API giúp quản lý state toàn cục dễ dàng hơn mà không cần phải truyền props qua nhiều lớp component.
2. Làm thế nào để giảm thiểu số lần re-render trong ứng dụng React của tôi?
Tách các component con thành các component độc lập và sử dụng memo để tối ưu hóa việc re-render.

Hãy bắt đầu tối ưu hóa ứng dụng của bạn ngay hôm nay để cải thiện hiệu suất và trải nghiệm người dùng!

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào