Hiểu về cách thức render của React

Mateo Garcia
Đang cập nhật

Tổng quan

ReactJS hiện nay đang là frontend framework được sử dụng rộng rãi nhất nhờ vào việc tiếp cận dễ dàng, tốc độ xử lý khá tốt, hiệu suất ổn định và đặc biệt là nó có một cộng đồng developer khổng lồ. Để chọn ngôn ngữ frontend để khám phá, có lẽ React rất đang được quan tâm ở hiện tại và tương lai, vì nó vẫn được đội ngũ Facebook team phát triển liên tục. Khi làm việc với React, bạn cần hiểu quá trình render của nó, một trong những yếu tố rất quan trọng quyết định đến hiệu suất ứng dụng của bạn. Vấn đề về hiệu suất có thể xuất hiện rất thường xuyên khi bạn phát triển một ứng dụng, nhất là ứng dụng đó có các component phức tạp , đòi hỏi re-render liên tục. Đó là lý do tại sao "Hiểu về cách thức render của React" được viết dành cho bạn.

Có 4 khái niệm quan trọng chúng ta sẽ đi qua :

  1. Virtual DOM
  2. Render
  3. Reconciliation (Sự đối chiếu)
  4. Commit

Virtual DOM (DOM ảo)

Virtual DOM được cho là một phiên bản thu nhỏ của DOM. Nó chứa tất cả thông tin cần thiết để tạo nên một DOM nhằm giải quyết những thay đổi mà DOM gặp phải khi sử dụng web hoặc ứng dụng di động. Việc render toàn bộ DOM khi có bất kỳ thay đổi nào mà DOM có thể trải qua: chèn / sửa đổi / xóa một phần tử hoặc các thuộc tính của nó sẽ trở nên quá tốn kém, đặc biệt khi ứng dụng trở nên phức tạp hơn. Vì vậy, Virtual DOM đại diện cho DOM trong bộ nhớ, có chức năng thực hiện tính toán việc sử dụng, và giám sát các stateprops để cuối cùng đưa ra quyết định thành phần nào của DOM thực sự nên được thay đổi (re-render)

DOM ảo (VDOM) là một khái niệm lập trình trong đó một đại diện của một DOM trên trang được lưu trong bộ nhớ và được đồng bộ hóa với DOM thật bởi thư viện ReactDOM. -- Official React --

ReactJS sử dụng thuật toán diff để tìm số bước tối thiểu để cập nhật DOM thực. Khi có các bước này, nó sẽ thực hiện tất cả các bước trong một vòng lặp sự kiện mà không liên quan đến DOM thật. Do đó, nếu có thêm phần tử trong DOM ảo được cập nhật, React js sẽ đợi vòng lặp sự kiện kết thúc, sau đó sẽ cập nhật hàng loạt đối với DOM thật.

Khi tất cả các bước được thực thi, React sẽ update DOM thật. Điều này có nghĩa là trong vòng lặp sự kiện, chỉ có một lần update DOM thật. Do đó, tất cả quá trình bố trí sẽ chỉ chạy vào đúng thời điểm để cập nhật DOM thực.

Render

Render là một quá trình được gọi khi có một sự thay đổi nào đó trong state của component xảy ra.

Khi một state nào đó thay đổi trong React, nó sẽ : - Thu thập root của tất cả các component trong ứng dụng (có thể hiểu là nó gom component nào có component chứa trong nó) để yêu cầu nó re-render lại vì state hoặc props đã bị thay đổi. - Thực hiện gọi những component này : 1. Nếu bạn sử dụng functional component, nó sẽ tự gọi lại hàm. 2. Nếu bạn sử dụng class component, no sẽ gọi Component.render()

Mặc dù khi quá trình render component đang diễn ra, DOM vẫn chưa được cập nhật và thay mới. Đến khi quá trình render này hoàn tất, DOM mới được cập nhật.

Bởi vì trong ReactJS, chùng ta thường sử dụng code viết bằng JSX, code này sẽ được chuyển thành React.createElement(...). Kết quả của createElement sẽ mô tả ứng dụng nên trông như thế nào trong phiên bản tiếp theo của quá trình render thông qua quá trình đối chiếu.

Reconciliation (Quá trình Đối chiếu)

Đây chính là phần nội dung chính mà mình muốn đề cập với các bạn và cụ thể đó là về cách mà React thực hiện việc so sánh 2 virtual DOM tree mới và cũ. Ở đây mình sẽ không nói về chi tiết từng bước từng bước mà React thực hiện việc so sánh này trên thực tế mà sẽ chỉ trình bày cho các bạn hiểu cơ bản về cách hoạt động của nó.

Khi thực hiện việc so sánh 2 Virtual DOM tree mới và cũ để update thì thuật toán của React sẽ cần đưa ra quyết đó là khi nào tái sử dụng lại Element đang có và khi nào cần tạo mới Element đó. Nếu bỏ qua việc tái sử dụng lại Element đang có thì sẽ dẫn đến vấn đề về hiệu năng vì React sẽ tiến hành tạo mới lại toàn bộ các Element mà trong khi nó có thể tái sử dụng lại và chỉ cần cập nhật thuộc tính hoặc phần children của Element đó.

Tại thời điểm này, hai đối tượng đang mô tả có cùng UI, React thông qua thuật toán heuristic bậc O (n ^ 3) sẽ có thể xác định phần tử nào cần được biểu diễn lại.

Một số khía cạnh giúp React nhận diện được component nào đang bị thay đổi :

  1. Element đã thay đổi kiểu phải được tạo lại.
  2. Các thay đổi trong thuộc tính của một phần tử được thay thế mà không cần ngắt kết nối phần tử.
  3. Cập nhật bên trong phần tử con sẽ tạo lại tất cả các phần tử con
  4. Các bản cập nhật trong các phần tử con sử dụng key làm thuộc tính được so sánh và chỉ các mục mới được đại diện.

Commit

Sau khi React tính toán tất cả những thay đổi cần thực hiện trong DOM tree, react-dom sẽ xuất hiện đối với trình duyệt web, đối với nền tảng mobile là react-native để thực hiện các sửa đổi. React sẽ dọn sạch các hiệu ứng trong layout cũ, trình duyệt sẽ tạo ra DOM, sau đó React sẽ chạy hiệu ứng layout mới.

Để giải thích về vòng đời trong React, bạn có thể xem qua sơ đồ sau:

Trước khi chuyển sang ví dụ, điều quan trọng là phải hiểu rằng trong điều kiện bình thường, nếu một component gọi render, nó sẽ tự động làm như vậy cho tất cả các thành phần con của nó. Tuy nhiên, có thể ngăn các thành phần nhất định được hiển thị lại trong một số trường hợp đặc biệt nhất định, React.PureComponent, React.memo, React.useMemoReact.useCallback.( Bạn có thể xem các bài viết về tối ưu hóa hiệu suất ứng dụng Reacttại đây).

Ví dụ

Xét ví dụ sau:

Code:

import * as React from "react";
import { useRenderTimes } from '../../utils';

function getRandomHEX() {
  return `#${Math.floor(Math.random() * 16777215).toString(16)}`;
}

function Header() {
  const [color, setColor] = React.useState("#111");
  const count = useRenderTimes();
  return (
    <header style={{ backgroundColor: color }}>
      <p>Header component has re-rendered {count} times</p>
      <button onClick={() => setColor(getRandomHEX())}>Click</button>
    </header>
  );
}

function Footer() {
  const count = useRenderTimes();
  return (
    <footer>
      <p>Footer component has re-rendered {count} times</p>
    </footer>
  );
}

function App() {
  const count = useRenderTimes();
  return (
    <>
      <Header />
      <main>
        <p>Hey, nice to see you again 👋🏼</p>
        <p>The App component has re-rendered {count} times</p>
      </main>
      <Footer />
    </>
  );
}

export default App;

useRenderTimes là một hook sẽ cho phép chúng ta tích lũy số lần component được hiển thị lại.

import * as React from 'react';

function useRenderTimes() {
  const renderRef = React.useRef(0);

  React.useEffect(() => {
    renderRef.current = renderRef.current + 1;
  });

  return renderRef.current;
}

export { useRenderTimes };

Thành phần

state riêng của nó, state này sẽ thay đổi khi chúng ta bắt đầu nhấn nút. Chúng ta hãy xem

Điều vừa xảy ra ở đây là: 1. Một sự kiện trong component <Header /> đã kích hoạt thay đổi trạng thái. Một kết xuất đã được lên lịch. 2. VirtualDOM bắt đầu phân tích thành phần nào được đánh dấu là cần được hiển thị lại, trong trường hợp này chỉ

cần nó. 3. Qua bước đối chiếu, chúng ta xác định rằng kiểu của
đang thay đổi. 4. Dispatch (yêu cầu thực thi) một commit đến DOM. 5. Và kết quả chúng ta thấy có sự thay đổi background của

Kết luận

Mặc dù quá trình render trong React là một quá trình có thể trở nên phức tạp, nhưng chúng ta phải công nhận công việc tuyệt vời của toàn bộ Nhóm React để cải thiện kinh nghiệm phát triển web hàng ngày. Việc tìm hiểu về quá trình này có thể hữu ích cho những người mới bắt đầu khám phá nó, cũng như cho những người đã sử dụng nó trong một thời gian dài và muốn hiểu những gì đang diễn ra đằng sau hậu trường. Điều mà trước nay ta chỉ thực hành và ít khi nào tự hỏi tại sao nó lại làm được như vậy.

Bài viết đến đây là hết, cám ơn các bạn đã theo dõi


Bài viết có liên quan