+2

React.memo và Shallow Compare

Giới thiệu

Chào tất cả các bạn. Hôm nay mình sẽ cùng tìm hiểu về một chủ đề khá thú vị về React, đó chính là memo, một khái niệm được React thêm vào từ version 16.6.

Trong React, việc re-render lại một component khi props và state thay đổi là cách để react update data mới cho component.

Tuy nhiên, khi xử lý với những component phức tạp, việc re-render nhiều lần sẽ trực tiếp ảnh hưởng tới hiệu suất của trang web.

Để tối ưu hiệu suất, cần phải tránh re-render những thứ không cần thiết. Các bạn cũng có thể tìm hiểu thêm về re-render trong link.

image.png

Dưới đây là một ví dụ

import { memo, useState } from 'react';

const Greeting = ({ name }) => {
  console.log("Greeting was rendered at", new Date().toLocaleTimeString());
  return <h3>Hello{name && ', '}{name}!</h3>;
};

export default function MyApp() {
  const [name, setName] = useState('');
  const [address, setAddress] = useState('');
  
  return (
    <>
      <label>
        Name{': '}
        <input value={name} onChange={e => setName(e.target.value)} />
      </label>
      <label>
        Address{': '}
        <input value={address} onChange={e => setAddress(e.target.value)} />
      </label>
      <Greeting name={name} />
    </>
  );
}

Trong ví dụ này, bất cứ khi nào name hoặc address thay đổi thì toàn bộ MyApp sẽ re-render khiến Greeting là childComponent cũng re-render theo.

Mặc dù address không liên quan tới Greeting nhưng khi khí address thay đổi thì Greeting vẫn sẽ re-render theo.

Để tối ưu cho trường hợp này, ta phải hạn chế Greeting re-render nhiều lần và sẽ chỉ re-render khi props của nó là name thay đổi.

Lấy ý tưởng từ PureComponent và shouldComponentUpdate với mục đích skip render lại nếu props không thay đổi, memo ra đời như một sự kế thừa khi có thể linh hoạt sử dụng trong functionComponent thay vì classComponent.

React.memo

memo lets you skip re-rendering a component when its props are unchanged.

Bây giờ với memo ta có thể control việc re-render của Greeting bằng việc wrapper nó.

Trong ví dụ này, memo sẽ chỉ re-render nếu props của nó là name thay đổi.

import { memo, useState } from 'react';

const Greeting = memo({ name }) => {
  console.log("Greeting was rendered at", new Date().toLocaleTimeString());
  return <h3>Hello{name && ', '}{name}!</h3>;
});

export default function MyApp() {
  const [name, setName] = useState('');
  const [address, setAddress] = useState('');
  
  return (
    <>
      <label>
        Name{': '}
        <input value={name} onChange={e => setName(e.target.value)} />
      </label>
      <label>
        Address{': '}
        <input value={address} onChange={e => setAddress(e.target.value)} />
      </label>
      <Greeting name={name} />
    </>
  );
}

Shallow Compare

Tương tự với PureComponent và shouldComponentUpdate, để nhận biết được sự thay đổi của props, memo cũng sử dụng so sánh Shallow Compare.

Shallow Compare hiểu đơn giản là so sánh nông sử dụng ===. Khi so sánh, ta cần đối chiếu giá trị và tham chiếu.

Với number, string, nó sẽ so sánh các giá trị của chúng. Tuy nhiên, khi so sánh các object, nó sẽ không so sánh các thuộc tính của chúng mà chỉ so sánh reference của chúng. Ví dụ

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

Bản chất của let a = {}; và let b = {} là tham chiếu đến 2 địa chỉ khác nhau nên khi so sánh a === b ta sẽ nhận được kết quả là false.

Việc này dẫn đến một số lỗi ta hay gặp phải khi mới tiếp cận với react và memo. Dưới đây là một ví dụ

import { memo, useState } from 'react';

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

export default function MyApp() {

  const [name, setName] = useState({firstName: '', lastName: ''});
 
  return (
    <>
      <label>
        First Name{': '}
        <input value={name.firstName} onChange={e => setName({...name, firstName: e.target.value})} />
      </label>
      <label>
        Last Name{': '}
        <input value={name.lastName} onChange={e => setName({...name, lastName: e.target.value})} />
      </label>
      <button onClick={() => setName({firstName: '', lastName: ''})} >Clear all</button >
      <Greeting name={name} />
    </>
  );
}

Trong trường hợp này, khi click vào button Clear all nhiều lần, ta sẽ thấy Greeting luôn re-render mặc dù giá trị name luôn bằng {firstName: '', lastName: ''}.

image.png

Khi gọi setName({firstName: '', lastName: ''})} tức là đã tạo 1 tham chiếu mới và tham chiếu nó tới với name vì vậy khi React.memo so sánh sẽ trả về kết quả true và re-render lại Greeting

Để khắc phục việc này, ta thể làm như sau:

const initData = {firstName: '', lastName: ''}
const [name, setName] = useState(initData);
...
 <button onClick={() => setName(initData)} >Clear all</button >
 ....

Việc này giúp tham chiếu tới 1 giá trị initData nên React.memo so sánh sẽ trả về kết quả false và không re-render.

References

https://react.dev/reference/react/memo

https://truyenmai.com/react/react-re-render


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.