Tiếng Việt

Hướng dẫn toàn diện về tối ưu hóa hiệu suất ứng dụng React bằng useMemo, useCallback và React.memo. Học cách ngăn chặn render lại không cần thiết và cải thiện trải nghiệm người dùng.

Tối Ưu Hóa Hiệu Suất React: Làm Chủ useMemo, useCallback và React.memo

React, một thư viện JavaScript phổ biến để xây dựng giao diện người dùng, nổi tiếng với kiến trúc dựa trên component và phong cách khai báo. Tuy nhiên, khi các ứng dụng phát triển phức tạp hơn, hiệu suất có thể trở thành một mối lo ngại. Việc render lại các component không cần thiết có thể dẫn đến hiệu suất chậm chạp và trải nghiệm người dùng kém. May mắn thay, React cung cấp một số công cụ để tối ưu hóa hiệu suất, bao gồm useMemo, useCallback, và React.memo. Hướng dẫn này sẽ đi sâu vào các kỹ thuật này, cung cấp các ví dụ thực tế và thông tin chi tiết hữu ích để giúp bạn xây dựng các ứng dụng React hiệu suất cao.

Tìm Hiểu về Re-render trong React

Trước khi đi sâu vào các kỹ thuật tối ưu hóa, điều quan trọng là phải hiểu tại sao việc re-render lại xảy ra trong React. Khi state hoặc props của một component thay đổi, React sẽ kích hoạt việc re-render component đó và có thể cả các component con của nó. React sử dụng một DOM ảo để cập nhật DOM thực một cách hiệu quả, nhưng việc re-render quá mức vẫn có thể ảnh hưởng đến hiệu suất, đặc biệt là trong các ứng dụng phức tạp. Hãy tưởng tượng một nền tảng thương mại điện tử toàn cầu nơi giá sản phẩm được cập nhật thường xuyên. Nếu không có tối ưu hóa, ngay cả một thay đổi giá nhỏ cũng có thể kích hoạt re-render trên toàn bộ danh sách sản phẩm, ảnh hưởng đến việc duyệt web của người dùng.

Tại Sao Component Bị Re-render

Mục tiêu của việc tối ưu hóa hiệu suất là ngăn chặn các lần re-render không cần thiết, đảm bảo rằng các component chỉ cập nhật khi dữ liệu của chúng thực sự đã thay đổi. Hãy xem xét một kịch bản liên quan đến việc trực quan hóa dữ liệu thời gian thực để phân tích thị trường chứng khoán. Nếu các thành phần biểu đồ re-render không cần thiết với mỗi lần cập nhật dữ liệu nhỏ, ứng dụng sẽ trở nên không phản hồi. Tối ưu hóa việc re-render sẽ đảm bảo trải nghiệm người dùng mượt mà và nhạy bén.

Giới thiệu useMemo: Ghi nhớ (Memoizing) các Tính toán Tốn kém

useMemo là một hook của React giúp ghi nhớ kết quả của một phép tính. Ghi nhớ (Memoization) là một kỹ thuật tối ưu hóa lưu trữ kết quả của các lần gọi hàm tốn kém và tái sử dụng các kết quả đó khi cùng một đầu vào xuất hiện trở lại. Điều này giúp ngăn chặn việc phải thực thi lại hàm một cách không cần thiết.

Khi nào nên dùng useMemo

Cách useMemo hoạt động

useMemo nhận hai đối số:

  1. Một hàm thực hiện phép tính.
  2. Một mảng các dependencies (phụ thuộc).

Hàm này chỉ được thực thi khi một trong các dependency trong mảng thay đổi. Nếu không, useMemo sẽ trả về giá trị đã được ghi nhớ trước đó.

Ví dụ: Tính dãy Fibonacci

Dãy Fibonacci là một ví dụ kinh điển về một phép tính nặng. Hãy tạo một component tính số Fibonacci thứ n bằng cách sử dụng useMemo.


import React, { useState, useMemo } from 'react';

function Fibonacci({ n }) {
  const fibonacciNumber = useMemo(() => {
    console.log('Calculating Fibonacci...'); // Minh họa thời điểm phép tính chạy
    function calculateFibonacci(num) {
      if (num <= 1) {
        return num;
      }
      return calculateFibonacci(num - 1) + calculateFibonacci(num - 2);
    }
    return calculateFibonacci(n);
  }, [n]);

  return 

Fibonacci({n}) = {fibonacciNumber}

; } function App() { const [number, setNumber] = useState(5); return (
setNumber(parseInt(e.target.value))} />
); } export default App;

Trong ví dụ này, hàm calculateFibonacci chỉ được thực thi khi prop n thay đổi. Nếu không có useMemo, hàm này sẽ được thực thi mỗi khi component Fibonacci re-render, ngay cả khi n không thay đổi. Hãy tưởng tượng phép tính này xảy ra trên một bảng điều khiển tài chính toàn cầu - mỗi nhịp biến động của thị trường đều gây ra một phép tính lại toàn bộ, dẫn đến độ trễ đáng kể. useMemo ngăn chặn điều đó.

Giới thiệu useCallback: Ghi nhớ (Memoizing) các Hàm

useCallback là một hook khác của React giúp ghi nhớ các hàm. Nó ngăn chặn việc tạo ra một instance hàm mới trong mỗi lần render, điều này có thể đặc biệt hữu ích khi truyền các callback làm props cho các component con.

Khi nào nên dùng useCallback

Cách useCallback hoạt động

useCallback nhận hai đối số:

  1. Hàm cần được ghi nhớ.
  2. Một mảng các dependencies (phụ thuộc).

Hàm này chỉ được tạo lại khi một trong các dependency trong mảng thay đổi. Nếu không, useCallback sẽ trả về cùng một instance hàm.

Ví dụ: Xử lý sự kiện nhấn nút

Hãy tạo một component với một nút bấm kích hoạt một hàm callback. Chúng ta sẽ sử dụng useCallback để ghi nhớ hàm callback này.


import React, { useState, useCallback } from 'react';

function Button({ onClick, children }) {
  console.log('Button re-rendered'); // Minh họa thời điểm Button re-render
  return ;
}

const MemoizedButton = React.memo(Button);

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

  const handleClick = useCallback(() => {
    console.log('Button clicked');
    setCount((prevCount) => prevCount + 1);
  }, []); // Mảng dependency rỗng có nghĩa là hàm chỉ được tạo một lần

  return (
    

Count: {count}

Increment
); } export default App;

Trong ví dụ này, hàm handleClick chỉ được tạo một lần vì mảng dependency rỗng. Khi component App re-render do thay đổi state count, hàm handleClick vẫn giữ nguyên. Component MemoizedButton, được bọc bởi React.memo, sẽ chỉ re-render nếu props của nó thay đổi. Vì prop onClick (handleClick) không thay đổi, component Button không re-render một cách không cần thiết. Hãy tưởng tượng một ứng dụng bản đồ tương tác. Mỗi khi người dùng tương tác, hàng tá component nút có thể bị ảnh hưởng. Nếu không có useCallback, những nút này sẽ re-render không cần thiết, tạo ra trải nghiệm ì ạch. Việc sử dụng useCallback đảm bảo tương tác mượt mà hơn.

Giới thiệu React.memo: Ghi nhớ (Memoizing) các Component

React.memo là một component bậc cao (higher-order component - HOC) giúp ghi nhớ một functional component. Nó ngăn component re-render nếu props của nó không thay đổi. Điều này tương tự như PureComponent đối với class component.

Khi nào nên dùng React.memo

Cách React.memo hoạt động

React.memo bọc một functional component và so sánh nông (shallowly compares) các props trước đó và props tiếp theo. Nếu các props giống nhau, component sẽ không re-render.

Ví dụ: Hiển thị hồ sơ người dùng

Hãy tạo một component hiển thị hồ sơ người dùng. Chúng ta sẽ sử dụng React.memo để ngăn chặn việc re-render không cần thiết nếu dữ liệu người dùng không thay đổi.


import React from 'react';

function UserProfile({ user }) {
  console.log('UserProfile re-rendered'); // Minh họa thời điểm component re-render
  return (
    

Name: {user.name}

Email: {user.email}

); } const MemoizedUserProfile = React.memo(UserProfile, (prevProps, nextProps) => { // Hàm so sánh tùy chỉnh (tùy chọn) return prevProps.user.id === nextProps.user.id; // Chỉ re-render nếu ID người dùng thay đổi }); function App() { const [user, setUser] = React.useState({ id: 1, name: 'John Doe', email: 'john.doe@example.com', }); const updateUser = () => { setUser({ ...user, name: 'Jane Doe' }); // Thay đổi tên }; return (
); } export default App;

Trong ví dụ này, component MemoizedUserProfile sẽ chỉ re-render nếu prop user.id thay đổi. Ngay cả khi các thuộc tính khác của đối tượng user thay đổi (ví dụ: tên hoặc email), component sẽ không re-render trừ khi ID khác đi. Hàm so sánh tùy chỉnh này trong `React.memo` cho phép kiểm soát chi tiết về thời điểm component re-render. Hãy xem xét một nền tảng mạng xã hội với các hồ sơ người dùng được cập nhật liên tục. Nếu không có `React.memo`, việc thay đổi trạng thái hoặc ảnh đại diện của người dùng sẽ gây ra re-render toàn bộ component hồ sơ, ngay cả khi các chi tiết cốt lõi của người dùng vẫn giữ nguyên. `React.memo` cho phép cập nhật có mục tiêu và cải thiện đáng kể hiệu suất.

Kết hợp useMemo, useCallback và React.memo

Ba kỹ thuật này hiệu quả nhất khi được sử dụng cùng nhau. useMemo ghi nhớ các phép tính tốn kém, useCallback ghi nhớ các hàm, và React.memo ghi nhớ các component. Bằng cách kết hợp các kỹ thuật này, bạn có thể giảm đáng kể số lần re-render không cần thiết trong ứng dụng React của mình.

Ví dụ: Một Component Phức tạp

Hãy tạo một component phức tạp hơn để minh họa cách kết hợp các kỹ thuật này.


import React, { useState, useCallback, useMemo } from 'react';

function ListItem({ item, onUpdate, onDelete }) {
  console.log(`ListItem ${item.id} re-rendered`); // Minh họa thời điểm component re-render
  return (
    
  • {item.text}
  • ); } const MemoizedListItem = React.memo(ListItem); function List({ items, onUpdate, onDelete }) { console.log('List re-rendered'); // Minh họa thời điểm component re-render return (
      {items.map((item) => ( ))}
    ); } const MemoizedList = React.memo(List); function App() { const [items, setItems] = useState([ { id: 1, text: 'Item 1' }, { id: 2, text: 'Item 2' }, { id: 3, text: 'Item 3' }, ]); const handleUpdate = useCallback((id) => { setItems((prevItems) => prevItems.map((item) => item.id === id ? { ...item, text: `Updated ${item.text}` } : item ) ); }, []); const handleDelete = useCallback((id) => { setItems((prevItems) => prevItems.filter((item) => item.id !== id)); }, []); const memoizedItems = useMemo(() => items, [items]); return (
    ); } export default App;

    Trong ví dụ này:

    Sự kết hợp các kỹ thuật này đảm bảo rằng các component chỉ re-render khi cần thiết, dẫn đến những cải thiện đáng kể về hiệu suất. Hãy tưởng tượng một công cụ quản lý dự án quy mô lớn, nơi danh sách các công việc liên tục được cập nhật, xóa và sắp xếp lại. Nếu không có những tối ưu hóa này, bất kỳ thay đổi nhỏ nào đối với danh sách công việc cũng sẽ gây ra một chuỗi re-render, làm cho ứng dụng chậm và không phản hồi. Bằng cách sử dụng chiến lược useMemo, useCallback, và React.memo, ứng dụng có thể duy trì hiệu suất ngay cả với dữ liệu phức tạp và cập nhật thường xuyên.

    Các Kỹ thuật Tối ưu hóa Bổ sung

    Mặc dù useMemo, useCallback, và React.memo là những công cụ mạnh mẽ, chúng không phải là lựa chọn duy nhất để tối ưu hóa hiệu suất React. Dưới đây là một vài kỹ thuật bổ sung cần xem xét:

    Những Lưu ý Toàn cầu về Tối ưu hóa

    Khi tối ưu hóa các ứng dụng React cho khán giả toàn cầu, điều quan trọng là phải xem xét các yếu tố như độ trễ mạng, khả năng của thiết bị và bản địa hóa. Dưới đây là một vài mẹo:

    Kết luận

    Tối ưu hóa hiệu suất ứng dụng React là rất quan trọng để mang lại trải nghiệm người dùng mượt mà và nhạy bén. Bằng cách làm chủ các kỹ thuật như useMemo, useCallback, và React.memo, và bằng cách xem xét các chiến lược tối ưu hóa toàn cầu, bạn có thể xây dựng các ứng dụng React hiệu suất cao có thể mở rộng để đáp ứng nhu cầu của một lượng người dùng đa dạng. Hãy nhớ phân tích ứng dụng của bạn để xác định các điểm nghẽn hiệu suất và áp dụng các kỹ thuật tối ưu hóa này một cách chiến lược. Đừng tối ưu hóa quá sớm – hãy tập trung vào những lĩnh vực mà bạn có thể đạt được tác động đáng kể nhất.

    Hướng dẫn này cung cấp một nền tảng vững chắc để hiểu và triển khai các tối ưu hóa hiệu suất React. Khi bạn tiếp tục phát triển các ứng dụng React, hãy nhớ ưu tiên hiệu suất và liên tục tìm kiếm những cách mới để cải thiện trải nghiệm người dùng.