Để làm chủ được ReactJS thì chúng ta sẽ cần phải hiểu sâu về nguyên lý hoạt động của nó, từ đó chúng ta mới có thể hiểu được những thành phần cơ bản như vòng đời của một thành phần hay quản lý trạng thái. Trong bài này Dũng sẽ càng các bạn tìm hiểu sâu một chút về bên trong thành phần core của ReactJS nhé.

Nguyên lý hoạt động

Về cơ bản thì cũng khá phức tạp để hiểu.

Bởi vì sử dụng virtual DOM, nghĩa là không có ngay các mã HTML để tương tác mà sử dụng Javascript để tạo ra các mã HTML nên ReactJS cần duy trì một đối tượng lập lịch chạy liên tục để kiểm tra có trạng thái thay đổi hay không thì sẽ render lại nội dung HTML.
Cụ thể hơn bạn có thể clone repo https://github.com/facebook/react sau đó mở dự án vừa clone được bằng Visual Studio Code và tìm kiếm theo các từ khoá sau:

.componentDidUpdate
updateClassInstance
updateClassComponent
mountLazyComponent
beginWork()
performUnitOfWork
workLoopConcurrent
renderRootSync
renderRootConcurrent
ensureRootIsScheduled
scheduleUpdateOnFiber

updateDehydratedSuspenseComponent
updateSuspenseComponent

performConcurrentWorkOnRoot
scheduleCallback

attemptEarlyBailoutIfNoScheduledUpdate

beginWork

packages/scheduler/src/forks/Scheduler.js
yield
shouldYieldToHost
frameInterval
frameYieldMs
function advanceTimers(currentTime) {
workLoop
flushWork
shouldYield
requestHostCallback
handleTimeout
requestHostTimeout
unstable_scheduleCallback
const localSetTimeout = typeof setTimeout === 'function' ? setTimeout : null;
const localClearTimeout =
schedulePerformWorkUntilDeadline = () => {
    localSetTimeout(performWorkUntilDeadline, 0);
};

Nó sẽ dẫn bạn đến tập tin Scheduler.js và đây chính là trái tim của ReactJS.

Tìm hiểu Scheduler.js

Trong mã nguồn của Scheduler.js, thời gian sleep để nhả tài nguyên cho CPU của scheduler (khung thời gian mà nó chờ trước khi tiếp tục thực hiện công việc) có thể được cấu hình và điều chỉnh thông qua các biến và hàm được sử dụng trong quá trình thực thi để tối ưu hiệu năng. Cụ thể, biến frameYieldMs xác định thời gian mà scheduler chờ trước khi tiếp tục thực hiện công việc tiếp theo.
Dưới đây là phần mã liên quan đến việc xác định và sử dụng thời gian sleep trong scheduler:

import {
  enableSchedulerDebugging,
  enableProfiling,
  frameYieldMs,  // Đây là biến xác định thời gian sleep
  userBlockingPriorityTimeout,
  lowPriorityTimeout,
  normalPriorityTimeout,
} from '../SchedulerFeatureFlags';

// ...

let frameInterval = frameYieldMs;
let startTime = -1;

function shouldYieldToHost(): boolean {
  const timeElapsed = getCurrentTime() - startTime;
  if (timeElapsed < frameInterval) {
    // The main thread has only been blocked for a really short amount of time;
    // smaller than a single frame. Dont yield yet.
    return false;
  }
  // Yield now.
  return true;
}

// ...

function performWorkUntilDeadline() {
  if (isMessageLoopRunning) {
    const currentTime = getCurrentTime();
    startTime = currentTime;

    let hasMoreWork = true;
    try {
      hasMoreWork = flushWork(currentTime);
    } finally {
      if (hasMoreWork) {
        schedulePerformWorkUntilDeadline();
      } else {
        isMessageLoopRunning = false;
      }
    }
  }
}

// ...

let schedulePerformWorkUntilDeadline;
if (typeof localSetImmediate === 'function') {
  schedulePerformWorkUntilDeadline = () => {
    localSetImmediate(performWorkUntilDeadline);
  };
} else if (typeof MessageChannel !== 'undefined') {
  const channel = new MessageChannel();
  const port = channel.port2;
  channel.port1.onmessage = performWorkUntilDeadline;
  schedulePerformWorkUntilDeadline = () => {
    port.postMessage(null);
  };
} else {
  schedulePerformWorkUntilDeadline = () => {
    localSetTimeout(performWorkUntilDeadline, 0);
  };
}

// ...

function requestHostTimeout(callback: (currentTime: number) => void, ms: number) {
  taskTimeoutID = localSetTimeout(() => {
    callback(getCurrentTime());
  }, ms);
}

// ...

  1. Biến frameYieldMs = 5:
  • Được khai báo trong SchedulerFeatureFlags.
  • Xác định khoảng thời gian mà scheduler sẽ chờ trước khi tiếp tục công việc. Đây là thời gian mà scheduler nhường quyền kiểm soát lại cho main thread để tránh việc chặn luồng này quá lâu.
  1. Hàm shouldYieldToHost:
  • Sử dụng biến frameYieldMs để xác định xem scheduler có nên nhường quyền kiểm soát lại cho luồng chính hay không.
  • Nếu thời gian đã trôi qua (timeElapsed) ít hơn frameYieldMs, scheduler sẽ không nhường (return false). Nếu không, scheduler sẽ nhường (return true).
  1. Hàm performWorkUntilDeadline:
  • Thực hiện công việc cho đến khi hết thời gian cho phép (deadline).
  • Sử dụng schedulePerformWorkUntilDeadline để lên lịch thực hiện công việc tiếp theo nếu còn công việc cần làm.
  1. Cấu hình schedulePerformWorkUntilDeadline:
  • Dựa vào môi trường (Node.js, trình duyệt cũ, môi trường DOM) để chọn phương pháp lập lịch phù hợp (setImmediate, MessageChannel, setTimeout).
    Thông qua các biến và hàm này, scheduler có thể quản lý thời gian sleep giữa các khoảng thời gian làm việc để đảm bảo hiệu suất và trải nghiệm người dùng tốt nhất.
    Có một điều đặc biệt là bạn không nhìn thấy hàm sleep hay delay nào trong mã nguồn điều này là do react sử dụng hàm setTimeout thay vì hàm setInterval, sleep hay delay.
    Cụ thể, các cơ chế lập lịch này được thực hiện thông qua requestHostCallback, requestHostTimeout, và schedulePerformWorkUntilDeadline. Dưới đây là những phần mã quan trọng liên quan đến cơ chế này:
  1. shouldYieldToHost:
    Hàm này xác định xem có nên nhường quyền kiểm soát lại cho luồng chính dựa trên thời gian đã trôi qua (frameInterval) hay không.
function shouldYieldToHost(): boolean {
  const timeElapsed = getCurrentTime() - startTime;
  if (timeElapsed < frameInterval) {
    // Main thread has only been blocked for a really short amount of time;
    // smaller than a single frame. Dont yield yet.
    return false;
  }
  // Yield now.
  return true;
}

  1. requestHostTimeoutcancelHostTimeout:
    Hai hàm này sử dụng setTimeout để lên lịch thực hiện callback sau một khoảng thời gian xác định.
function requestHostTimeout(
  callback: (currentTime: number) => void,
  ms: number,
) {
  taskTimeoutID = localSetTimeout(() => {
    callback(getCurrentTime());
  }, ms);
}

function cancelHostTimeout() {
  localClearTimeout(taskTimeoutID);
  taskTimeoutID = ((-1: any): TimeoutID);
}
  1. performWorkUntilDeadlineschedulePerformWorkUntilDeadline:
    Hàm performWorkUntilDeadline thực hiện công việc và gọi schedulePerformWorkUntilDeadline để tiếp tục công việc khi cần thiết.
const performWorkUntilDeadline = () => {
  if (isMessageLoopRunning) {
    const currentTime = getCurrentTime();
    startTime = currentTime;

    let hasMoreWork = true;
    try {
      hasMoreWork = flushWork(currentTime);
    } finally {
      if (hasMoreWork) {
        schedulePerformWorkUntilDeadline();
      } else {
        isMessageLoopRunning = false;
      }
    }
  }
};

let schedulePerformWorkUntilDeadline;
if (typeof localSetImmediate === 'function') {
  schedulePerformWorkUntilDeadline = () => {
    localSetImmediate(performWorkUntilDeadline);
  };
} else if (typeof MessageChannel !== 'undefined') {
  const channel = new MessageChannel();
  const port = channel.port2;
  channel.port1.onmessage = performWorkUntilDeadline;
  schedulePerformWorkUntilDeadline = () => {
    port.postMessage(null);
  };
} else {
  schedulePerformWorkUntilDeadline = () => {
    localSetTimeout(performWorkUntilDeadline, 0);
  };
}

Tổng kết

Như vậy chúng ta đã cùng nhau tìm hiểu một chút sâu bên trong lõi của ReactJS cụ thể là nguyên lý hoạt động và lớp Scheduler.js.


Cám ơn bạn đã quan tâm đến bài viết|video này. Để nhận được thêm các kiến thức bổ ích bạn có thể:

  1. Đọc các bài viết của TechMaster trên facebook: https://www.facebook.com/techmastervn
  2. Xem các video của TechMaster qua Youtube: https://www.youtube.com/@TechMasterVietnam nếu bạn thấy video/bài viết hay bạn có thể theo dõi kênh của TechMaster để nhận được thông báo về các video mới nhất nhé.
  3. Chat với techmaster qua Discord: https://discord.gg/yQjRTFXb7a