0
0
Lập trình
Flame Kris
Flame Krisbacodekiller

Tối Ưu Hiệu Suất React Với React.memo và So Sánh Nông (Shallow Compare)

Đăng vào 3 tuần trước

• 4 phút đọc

Giới thiệu

Chào các bạn! Hôm nay, chúng ta sẽ khám phá một chủ đề rất thú vị trong React: memo, một khái niệm mới được giới thiệu từ phiên bản 16.6.

Trong React, việc re-render một component khi props và state thay đổi là cơ chế chính giúp React cập nhật dữ liệu mới cho component. Tuy nhiên, khi làm việc với các component phức tạp, việc re-render liên tục có thể làm giảm hiệu suất của ứng dụng. Do đó, để tối ưu hóa hiệu suất, cần tránh việc re-render những component không cần thiết. Các bạn có thể tìm hiểu sâu hơn về vấn đề này qua liên kết bên dưới.

Ví dụ Minh Họa

jsx Copy
import { useState } from 'react';

const Greeting = ({ name }) => {
  console.log("Greeting được render lúc", new Date().toLocaleTimeString());
  return <h3>Xin chào{name && ', '}{name}!</h3>;
};

export default function MyApp() {
  const [name, setName] = useState('');
  const [address, setAddress] = useState('');
  
  return (
    <>  
      <label>
        Tên{': '}
        <input value={name} onChange={e => setName(e.target.value)} />
      </label>
      <label>
        Địa chỉ{': '}
        <input value={address} onChange={e => setAddress(e.target.value)} />
      </label>
      <Greeting name={name} />
    </>
  );
}

Trong ví dụ trên, bất cứ khi nào name hoặc address thay đổi, toàn bộ MyApp sẽ re-render và do đó, Greeting cũng bị re-render. Mặc dù address không liên quan đến Greeting, nhưng sự thay đổi của nó vẫn khiến Greeting được render lại.

Để tối ưu hóa trường hợp này, chúng ta cần ngăn chặn Greeting re-render khi props của nó không thay đổi, chỉ khi name thay đổi.

React.memo

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

memo cho phép bạn bỏ qua việc re-render một component khi props không thay đổi.

Giờ đây, với memo, ta có thể kiểm soát việc re-render của Greeting bằng cách bọc component này.

jsx Copy
import { memo, useState } from 'react';

const Greeting = memo(({ name }) => {
  console.log("Greeting được render lúc", new Date().toLocaleTimeString());
  return <h3>Xin chào{name && ', '}{name}!</h3>;
});

export default function MyApp() {
  const [name, setName] = useState('');
  const [address, setAddress] = useState('');
  
  return (
    <>  
      <label>
        Tên{': '}
        <input value={name} onChange={e => setName(e.target.value)} />
      </label>
      <label>
        Địa chỉ{': '}
        <input value={address} onChange={e => setAddress(e.target.value)} />
      </label>
      <Greeting name={name} />
    </>
  );
}

Trong ví dụ này, memo sẽ chỉ re-render Greeting khi props là name có sự thay đổi.

Shallow Compare

Cũng giống như PureComponentshouldComponentUpdate, để phát hiện sự thay đổi của props, memo sử dụng phương pháp so sánh nông (Shallow Compare).

Shallow Compare hoạt động như thế nào?

Shallow Compare thực chất là so sánh nông thông qua ===. Đối với số, chuỗi, nó so sánh giá trị của chúng. Còn khi so sánh các đối tượng, nó chỉ so sánh địa chỉ tham chiếu, không so sánh thuộc tính bên trong. Ví dụ:

javascript Copy
1 === 1 // true
{} === {} // false 
[] === [] // false 
{a:1} === {a:1} // false

Vấn đề xảy ra khi chúng ta có một trạng thái là đối tượng. Nếu chúng ta tạo một đối tượng mới mỗi khi cập nhật state, thì React.memo sẽ phát hiện có sự thay đổi và re-render lại component, ngay cả khi nội dung không thay đổi.

Một Ví Dụ Cụ Thể

jsx Copy
import { memo, useState } from 'react';

const Greeting = memo(({ name }) => {
  console.log(name);
  return <h3>Xin chào {name.firstName} {name.lastName}!</h3>;
});

export default function MyApp() {
  const [name, setName] = useState({firstName: '', lastName: ''});
  
  return (
    <>  
      <label>
        Tên{': '}
        <input value={name.firstName} onChange={e => setName({...name, firstName: e.target.value})} />
      </label>
      <label>
        Họ{': '}
        <input value={name.lastName} onChange={e => setName({...name, lastName: e.target.value})} />
      </label>
      <button onClick={() => setName({firstName: '', lastName: ''})} >Xóa tất cả</button>
      <Greeting name={name} />
    </>
  );
}

Trong trường hợp này, mỗi khi nhấn nút Xóa tất cả, Greeting vẫn sẽ bị re-render dù giá trị cuối cùng là {firstName: '', lastName: ''} vì mỗi lần setName đều tạo ra một tham chiếu mới.

Giải pháp khắc phục

Để khắc phục điều này, bạn có thể làm như sau:

jsx Copy
const initData = {firstName: '', lastName: ''};
const [name, setName] = useState(initData);
...
<button onClick={() => setName(initData)} >Xóa tất cả</button>
...

Bằng cách này, bạn sử dụng chung một tham chiếu tới initData, giúp React.memo so sánh và nhận ra rằng không cần phải re-render Greeting.

Kết Luận

Việc sử dụng React.memo và hiểu về Shallow Compare là rất quan trọng trong việc tối ưu ứng dụng React của bạn. Hi vọng bài viết này đã cung cấp cho bạn những kiến thức bổ ích!

Tài Liệu Tham Khảo

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