从Fiber到requestAnimationFrame

本文从React中的Fiber技术出发,使用requestAnimationFrame方法展示Fiber的设计理念,然后对两者进行了对比。

1. Fiber 是什么?

React 的 Fiber 架构通过使用工作单元系统,在协调和渲染过程中分割任务。它的相关概念如下:

1.1. Fiber:是一个工作单元

  • Fiber 对象: 每个 React 元素都有其自己的 Fiber 对象。Fiber 是一个 JavaScript 对象,包含有关组件及其输入和输出的信息。
  • 工作分割: 在 Fiber 中,渲染和协调的工作被划分为小单元。每个 Fiber 代表一个工作单元。

1.2. 增量渲染

  • 分解工作: 与一次性处理整个组件树不同,Fiber 将其分解为更小的块。
  • 让渡给浏览器: 在这些块之间,Fiber 可以将控制权让渡给浏览器。这允许浏览器处理其他重要任务,如布局、绘制和处理用户输入。

1.3. 任务优先级

  • 优先处理工作: 并非所有更新都具有相同的优先级。由用户发起的更新(例如,在输入框中输入)相比于后台更新具有更高的优先级。
  • 调度: React 的调度器根据紧急性对任务进行优先级排序。高优先级的更新首先被处理。

1.4. 工作循环

  • 进行中的工作树: 渲染过程中,React 保持一个进行中的工作树。如果出现更重要的事情,它可以暂停这棵树上的工作。
  • 恢复工作: 处理完高优先级任务后,React 可以从暂停的地方继续。

1.5. 离屏工作和 Suspense

  • 离屏渲染: React 可以在后台(离屏)准备新的 UI,同时保持当前 UI 的交互性。
  • Suspense: React 的 Suspense 功能利用 Fiber 暂停等待数据的组件渲染,并显示回退界面。

1.6. 并发

  • 并发模式: 通过 Fiber 启用,该模式允许 React 同时准备多个版本的 UI。如果出现更重要的更新,React 可以中断一棵树的渲染。

小结

Fiber 的任务分割方式根本改变了 React 处理更新的方式。它通过有效管理渲染任务、优先考虑用户交互并最小化 UI 阻塞,为复杂应用提供了更平滑的用户体验。这个系统给予 React 对更新过程的细致控制,使其更加响应快速和高效能。

2. 如何实践分割概念?

React 的 Fiber 架构背后的原理可以应用于 JavaScript 编程,以提高性能,特别是在处理可能阻塞主线程的操作时。下面的例子说明如何在 JavaScript 代码中使用类似的概念:

2.1. 背景:

假设有一个 JavaScript 函数,用于处理大量数据数组。这种繁重的计算可能会阻塞主线程,导致用户体验差,尤其是在 UI 应用程序中。

2.2. 传统方法:

function processData(data) {
    // 繁重计算
    data.forEach(item => {
        // 处理每个项目
    });
    // 如果数据量大,这可能会阻塞主线程
}

2.3. 仿 Fiber 的概念:

  1. 分解任务:
    将繁重的任务分解成更小的块,类似于 Fiber 如何分解渲染任务。

  2. 让渡控制权:
    使用 setTimeoutrequestAnimationFrame 让控制权回归给浏览器,允许浏览器处理用户输入和其他任务,类似于 Fiber 如何优先处理任务。

  3. 分批处理数据:
    分批而不是一次性处理所有数据。

2.4. 示例:

function processInChunks(data, chunkSize, callback) {
    let index = 0;

    function processChunk() {
        const chunkEnd = Math.min(index + chunkSize, data.length);
        for (; index < chunkEnd; index++) {
            // 处理每个项目
        }
        if (index < data.length) {
            // 让渡控制权并安排下一个数据块
            requestAnimationFrame(processChunk);
        } else {
            callback(); // 所有数据已处理
        }
    }

    processChunk();
}

// 使用示例
processInChunks(largeDataArray, 100, () => {
    console.log('处理完成');
});

2.5. 代码解释:

  • processInChunks 函数: 此函数接收数据数组、一个数据块大小和在所有数据处理完后调用的回调函数。
  • processChunk: 这个内部函数处理一块数据。它使用 requestAnimationFrame 将处理过程分布在多个帧上,允许浏览器保持响应。
  • 回调: 一旦所有数据块都被处理,就会执行回调函数。

2.6. 核心部分:

上面代码中最重要的部分是:

// 让渡控制权并安排下一个数据块
requestAnimationFrame(processChunk);

更深入地了解 requestAnimationFrame(processChunk) 的工作机制:

  1. 让渡控制权回归给浏览器:

    • 当调用 requestAnimationFrame(processChunk) 时,实际上是在告诉浏览器:“请在下一次重绘之前运行 processChunk。”
    • 重绘通常以每秒 60 次(60 FPS)的速率发生,尽管这可能因设备和其性能而异。
    • 调用后,控制权返回给浏览器。重要的是要注意 requestAnimationFrame 是非阻塞的。这意味着浏览器可以自由地执行其他任务,例如处理用户输入、动画或重新渲染 UI 组件。
  2. 如何安排下一个数据块:

    • 一旦发出 requestAnimationFrame(processChunk) 调用,浏览器会将 processChunk 函数放入其动画帧队列中。
    • 浏览器然后继续其事件循环,处理其他任务、事件和可能来自应用程序其他部分的其他已安排的动画帧。
    • 当浏览器准备好重绘屏幕时(通常在下一个帧中发生),它会检查其动画帧队列。
    • 如果到了下一次重绘的时候,且 processChunk 在队列中,浏览器执行 processChunk
    • processChunk 再次被调用时,浏览器可能已经处理了用户事件、更新了动画或重新绘制了 UI。这使应用程序感觉更加响应。
  3. 继续处理过程:

    • processChunk 函数内部,完成一块工作后,它再次调用 requestAnimationFrame(processChunk),重复这个过程。
    • 这创建了一个循环,在该循环中 processChunk 做了一些工作,然后将控制权让渡给浏览器,然后在下一个帧中再次被调用。
    • 这个循环持续到所有工作完成,允许浏览器在这些数据块之间插入其他任务。
  4. 为什么这很重要:

    • 在 JavaScript 和网页开发中,保持 UI 的响应性至关重要。长时间的、不间断的 JavaScript 任务可能会导致主线程阻塞,从而导致用户体验缓慢。
    • 通过将任务分解成更小的数据块,并使用 requestAnimationFrame 来安排这些数据块,你允许浏览器保持响应。即使在后台处理大型任务时,它也可以顺畅地处理用户交互、动画和重绘。

requestAnimationFrame(processChunk) 是一种平衡繁重的 JavaScript 工作负载与浏览器保持对用户交互和其他任务响应性需求的技术。这是一种与浏览器的事件循环和渲染周期协作的方式,确保 JavaScript 不会垄断主线程。

2.7. 优势:

  • 非阻塞: 通过分批处理数据并将控制权让渡给浏览器,主线程保持响应。
  • 用户体验: 这种方法提高了 UI 应用程序的用户体验,因为它防止了在繁重计算期间界面冻结。
  • 灵活性: 可以根据任务的性质和环境的性能特征调整数据块大小。

此例中用到的方法,受到了 React 的 Fiber 启发,展示了如何理解流行库背后的基本原理,是一种更好的编码实践。

2.8. 完整示例:

function processData(data, chunkSize) {
    return new Promise(resolve => {
        processInChunks(data, chunkSize, () => {
            console.log('Processing complete');
            resolve();
        });
    });
}

function processInChunks(data, chunkSize, callback) {
    let index = 0;

    function processChunk() {
        const chunkEnd = Math.min(index + chunkSize, data.length);
        for (; index < chunkEnd; index++) {
            // Example processing (can be any computation)
            data[index] = data[index] * 2; // Just an example computation
        }
        if (index < data.length) {
            // Yield control and schedule the next chunk
            requestAnimationFrame(processChunk);
        } else {
            callback(); // All data processed
        }
    }

    processChunk();
}

// Usage Example
const largeDataArray = new Array(10000).fill(null).map((_, i) => i); // Large array of numbers
const chunkSize = 100; // Process 100 items at a time

processData(largeDataArray, chunkSize)
    .then(() => {
        console.log('All data processed:', largeDataArray);
    });

3. RequestAnimationFrame 与计时器相比的核心优势是什么?

requestAnimationFrame 自动控制执行间隔的特性是其有效性的关键,特别是在处理耗时的函数或复杂动画时。这使它提供了多个优势:

  1. 与屏幕刷新率同步: 通过使函数执行与屏幕的刷新率对齐,requestAnimationFrame 确保更新在合适的时间发生,从而使动画和 UI 更改更加流畅。这避免了使用传统定时器可能发生的帧跳过或卡顿。

  2. 有效利用系统资源: 由于 requestAnimationFrame 将代码执行延迟到浏览器渲染过程的最佳时机,它可以帮助更有效地利用系统资源。这对于电池供电的设备特别有益,因为可以最小化不必要的处理。

  3. 自适应帧率: requestAnimationFrame 能够根据浏览器和系统当前的负载情况适应性调整,以维持性能。如果系统负载过重,它可能会降低帧率,以确保在不压倒系统的情况下保持一致的性能。

  4. 非活动时暂停: requestAnimationFrame 在浏览器标签页或窗口不可见或不活动时会自动暂停。这有助于减少在更新不需要时的不必要计算和资源使用。

  5. 处理长任务: 在处理长任务时,将它们分解为更小的块,并使用 requestAnimationFrame,可以让浏览器在这些任务与其渲染操作之间交错进行。这种方法确保浏览器保持响应性,避免了长时间不间断的 JavaScript 执行可能导致的卡顿和不响应。

requestAnimationFrame 的“神奇之处”在于它能够智能且高效地管理函数执行的时机,与浏览器的渲染周期和谐协作,显著提高了 web 应用程序的性能和用户体验。

4. RequestAnimationFrame 与 Fiber 的比较

React 中使用的 Fiber 架构并不直接使用 requestAnimationFrame。提供的 requestAnimationFrame 示例旨在以简化的方式说明类似的概念。

与 requestAnimationFrame 的关系:

  1. 概念相似性: Fiber 和 requestAnimationFrame 都共享将繁重计算分解为更小块并有效安排这些任务以提高性能和响应性的类似概念。

  2. 不同的实现方式: 然而,它们的实际实现和用例不同。requestAnimationFrame 是一个浏览器 API,主要用于动画和视觉更新,确保它们与浏览器的重绘周期同步。与此相反,React Fiber 是一个更全面的框架级架构,设计用于高效更新 React 应用程序中的 UI 和管理状态变化。

尽管 React Fiber 和 requestAnimationFrame 都旨在优化性能和响应性,但它们在不同的范围内以不同的机制运作。Fiber 特定于 React 的内部渲染引擎,专注于在 React 应用程序中高效渲染和更新 UI。


English version: From Fiber to RequestAnimationFrame

1. What’s the Fiber?

React’s Fiber architecture splits tasks in its reconciliation and rendering process by using a unit of work system. Here’s an overview of how it manages to split tasks:

1.1. Fiber: A Unit of Work

  • Fiber Object: Each React element gets its own Fiber object. A Fiber is a JavaScript object that contains information about a component, its input, and its output.
  • Work Division: In Fiber, the work of rendering and reconciliation is divided into small units. Each Fiber represents a unit of work.

1.2. Incremental Rendering

  • Breaking Down Work: Instead of processing the entire component tree in one go, Fiber breaks it down into smaller chunks.
  • Yielding to the Browser: Between these chunks, Fiber can yield control back to the browser. This allows the browser to handle other important tasks like layout, paint, and handling user inputs.

1.3. Task Prioritization

  • Prioritizing Work: Not all updates have the same priority. User-initiated updates (like typing in an input) have higher priority compared to background updates.
  • Scheduling: React’s scheduler prioritizes tasks based on urgency. High-priority updates are processed first.

1.4. Work Loop

  • Work-in-Progress Tree: React maintains a work-in-progress tree during rendering. It can pause work on this tree if something more important comes up.
  • Resuming Work: After handling higher priority tasks, React can resume where it left off.

1.5. Offscreen Work and Suspense

  • Offscreen Rendering: React can prepare new UI in the background (offscreen) while the current UI is still interactive.
  • Suspense: React’s Suspense feature leverages Fiber to pause rendering components that are waiting for data, and display fallbacks.

1.6. Concurrency

  • Concurrent Mode: Enabled by Fiber, this mode allows React to prepare multiple versions of the UI at the same time. React can interrupt rendering one tree if a more important update comes along.

Briefing Summary

Fiber’s approach to splitting tasks fundamentally changed how React handles updates. It provides a smoother user experience, especially for complex applications, by efficiently managing rendering tasks, prioritizing user interactions, and minimizing UI blocking. This system gives React fine-grained control over the update process, making it more responsive and performant.

2. How to Practice The Spliting Concept?

The principles behind React’s Fiber architecture can be applied to JavaScript programming in general to improve performance, especially when dealing with operations that might block the main thread. Here’s an example illustrating how you can use similar concepts in your JavaScript code:

2.1. Problem:

Suppose you have a JavaScript function that processes a large array of data. This heavy computation can block the main thread, leading to a poor user experience, especially in UI applications.

2.2. Traditional Approach:

function processData(data) {
    // Heavy computation
    data.forEach(item => {
        // process item
    });
    // This can block the main thread if data is large
}

2.3. Applying Fiber-like Concepts:

  1. Break Down the Task:
    Split the heavy task into smaller chunks, similar to how Fiber breaks down rendering tasks.

  2. Yield Control:
    Use setTimeout or requestAnimationFrame to yield control back to the browser, allowing it to handle user inputs and other tasks, similar to how Fiber prioritizes tasks.

  3. Process in Chunks:
    Process the data in smaller batches rather than all at once.

2.4. Example Implementation:

function processInChunks(data, chunkSize, callback) {
    let index = 0;

    function processChunk() {
        const chunkEnd = Math.min(index + chunkSize, data.length);
        for (; index < chunkEnd; index++) {
            // process item
        }
        if (index < data.length) {
            // Yield control and schedule the next chunk
            requestAnimationFrame(processChunk);
        } else {
            callback(); // All data processed
        }
    }

    processChunk();
}

// Usage
processInChunks(largeDataArray, 100, () => {
    console.log('Processing complete');
});

2.5. Explanation:

  • processInChunks Function: This function takes the data array, a chunk size, and a callback to be called once all data is processed.
  • processChunk: This inner function processes a chunk of data. It uses requestAnimationFrame to break up the processing over multiple frames, allowing the browser to stay responsive.
  • Callback: Once all chunks are processed, the callback function is executed.

2.6. The Most Important Part:

The most important of the code above is:

// Yield control and schedule the next chunk 
requestAnimationFrame(processChunk);

To delve deeper into how requestAnimationFrame(processChunk) works, let’s explore its mechanism in detail:

  1. Yielding Control Back to the Browser:

    • When you call requestAnimationFrame(processChunk), you’re essentially telling the browser: “Please run processChunk before you do your next repaint.”
    • Repaints usually happen at a rate of 60 times per second (60 FPS), although this can vary based on the device and its capabilities.
    • After this call, the control is returned to the browser. It’s important to note that requestAnimationFrame is non-blocking. This means that the browser is free to perform other tasks, such as handling user inputs, animations, or re-rendering UI components.
  2. How It Schedules the Next Chunk:

    • Once you’ve made the requestAnimationFrame(processChunk) call, the browser puts the processChunk function in its animation frame queue.
    • The browser then continues with its event loop, handling other tasks, events, and potentially other scheduled animation frames from different parts of the application.
    • When the browser is ready to repaint the screen, which typically happens in the next frame, it checks its animation frame queue.
    • If it’s time for the next repaint and processChunk is in the queue, the browser executes processChunk.
    • By the time processChunk is called again, the browser might have handled user events, updated animations, or repainted the UI. This is what makes the application feel more responsive.
  3. Continuing the Process:

    • Inside the processChunk function, after a chunk of work is done, it calls requestAnimationFrame(processChunk) again, repeating the process.
    • This creates a loop where processChunk does a bit of work, then yields control back to the browser, and then gets called again in the next frame.
    • This cycle continues until all the work is done, allowing other browser tasks to interleave between these chunks.
  4. Why This Matters:

    • In JavaScript and web development, keeping the UI responsive is crucial. Long, uninterrupted JavaScript tasks can block the main thread, leading to a sluggish user experience.
    • By breaking tasks into smaller chunks and using requestAnimationFrame to schedule these chunks, you allow the browser to stay responsive. It can handle user interactions, animations, and repaints smoothly, even while a large task is being processed in the background.

In summary, requestAnimationFrame(processChunk) is a technique to balance a heavy JavaScript workload with the browser’s need to remain responsive to user interactions and other tasks. It’s a way of cooperating with the browser’s event loop and rendering cycle, ensuring that your JavaScript doesn’t monopolize the main thread.

2.7. Benefits:

  • Non-blocking: By processing data in chunks and yielding control back to the browser, the main thread remains responsive.
  • User Experience: This approach improves the user experience in UI applications, as it prevents the interface from freezing during heavy computations.
  • Flexibility: You can adjust the chunk size based on the nature of the task and the performance characteristics of the environment.

This method, inspired by React’s Fiber, demonstrates how understanding underlying principles of popular libraries can lead to better coding practices in general JavaScript development.

2.8. Full Example:

function processData(data, chunkSize) {
    return new Promise(resolve => {
        processInChunks(data, chunkSize, () => {
            console.log('Processing complete');
            resolve();
        });
    });
}

function processInChunks(data, chunkSize, callback) {
    let index = 0;

    function processChunk() {
        const chunkEnd = Math.min(index + chunkSize, data.length);
        for (; index < chunkEnd; index++) {
            // Example processing (can be any computation)
            data[index] = data[index] * 2; // Just an example computation
        }
        if (index < data.length) {
            // Yield control and schedule the next chunk
            requestAnimationFrame(processChunk);
        } else {
            callback(); // All data processed
        }
    }

    processChunk();
}

// Usage Example
const largeDataArray = new Array(10000).fill(null).map((_, i) => i); // Large array of numbers
const chunkSize = 100; // Process 100 items at a time

processData(largeDataArray, chunkSize)
    .then(() => {
        console.log('All data processed:', largeDataArray);
    });

3. What’s the Core Advantage of RequestAnimalFrame over Timer?

The automatic gap control between executions is a crucial aspect of requestAnimationFrame and is central to its effectiveness, especially in handling time-consuming functions or complex animations. This feature allows it to offer several advantages:

  1. Synchronization with Screen Refresh Rate: By aligning function executions with the screen’s refresh rate, requestAnimationFrame ensures that updates happen at the right time, leading to smoother animations and UI changes. This avoids the problem of frame skipping or stuttering that can occur with traditional timers.

  2. Efficient Use of System Resources: Since requestAnimationFrame delays the execution of code to moments optimal for the browser’s rendering process, it can help in more efficient utilization of system resources. This is particularly beneficial for battery-powered devices, as unnecessary processing is minimized.

  3. Adaptive Frame Rate: The ability of requestAnimationFrame to adapt to the current load on the browser and the system means that it can dynamically adjust to maintain performance. If the system is under heavy load, it may reduce the frame rate to ensure consistent performance without overwhelming the system.

  4. Pausing When Inactive: Another important aspect is that requestAnimationFrame automatically pauses when the browser tab or window is not visible or active. This helps in reducing unnecessary computations and resource usage when the updates are not needed.

  5. Handling Long Tasks: When dealing with long tasks, breaking them into smaller chunks and using requestAnimationFrame allows the browser to interleave these tasks with its rendering operations. This approach ensures that the browser remains responsive, avoiding the jank and unresponsiveness that can occur with long, uninterrupted JavaScript execution.

In summary, the “magic” of requestAnimationFrame lies in its ability to intelligently and efficiently manage the timing of function executions in harmony with the browser’s rendering cycle, significantly improving the performance and user experience of web applications.

4. Comparation Between RequestAnimationFrame And Fiber

The Fiber architecture used in React does not directly use requestAnimationFrame. Instead, the example I provided with requestAnimationFrame was meant to illustrate a similar concept in a simplified manner. Let’s clarify the relationship and differences:

Relationship with requestAnimationFrame:

  1. Conceptual Similarity: Both Fiber and requestAnimationFrame share a similar concept of managing heavy computation by breaking it down into smaller chunks and efficiently scheduling these tasks to improve performance and responsiveness.

  2. Different Implementations: However, the actual implementation and use cases are different. requestAnimationFrame is a browser API mainly used for animations and visual updates, ensuring they are synchronized with the browser’s repaint cycle. In contrast, React Fiber is a more comprehensive framework-level architecture designed for efficiently updating the UI and managing state changes in a React application.

In summary, while React Fiber and requestAnimationFrame both aim to optimize performance and responsiveness, they operate in different scopes and with different mechanisms. Fiber is specific to React’s internal rendering engine, focusing on efficiently rendering and updating the UI in a React application.

原文链接:https://juejin.cn/post/7334141485028147209 作者:慕仲卿

(0)
上一篇 2024年2月14日 上午10:30
下一篇 2024年2月14日 上午10:41

相关推荐

发表回复

登录后才能评论