React16_理念


React16_理念

React15架构导致卡顿

Reconciler(协调器)操作虚拟DOM

  1. 调用JSX生成虚拟DOM

  2. 新旧虚拟DOM对比与更新

  3. 通知 Renderer(渲染器)执行

Renderer(渲染器)生成真实DOM

不同的Renderer,将虚拟DOM生成不同的平台数据结构

React15的缺点: Reconciler同步递归更新不可中断 导致卡顿

如果中途中断函数调用,则执行栈销毁,无法复用之前的中间状态。

而 JS执行与Paint任务都发生在主线程, 是互斥的, 如果 JS 执行时间过长 导致一直占用主线程, 引起浏览器卡顿

且 递归调用 调用栈太深时 资源占用高

React16如何解决卡顿

React16核心目的 : 将 同步递归不可中断更新架构 变为 异步可中断更新架构

React16.13 开始中使用了 Fiber 架构, 将 React核心算法 重新实现。

将渲染工作分割成更小的Fiber单元,并将其分散到多个帧(渐进式渲染),适时让渡执行权.

在 浏览器每帧的空闲阶段 执行 Fiber单元,再每个Fiber单元执行完后 检查当前帧剩余时间,

如果当前帧已到时限则停止执行, 等待下一帧空闲时间继续, 直到 当前 Reconciler 执行完毕.

React16架构是什么

使用 fiber 重构 Reconciler,引入 Scheduler

Scheduler(调度器)—— 判断浏览器帧空闲时间,判断任务优先级,任务中断与开始
Reconciler(协调器)—— 操作虚拟DOM,标记出变更
Renderer(渲染器)—— 将虚拟DOM的变更体现在真实DOM上
Scheduler Reconciler 随时可因 更高优先级任务 当前帧剩余时间不足 中止

render阶段,Reconciler工作的阶段。
commit阶段,Renderer工作的阶段。
render与commit阶段统称为work,即React在工作中。
相对应的,如果任务正在Scheduler内调度,就不属于work。

Scheduler

React 之从 requestIdleCallback 到时间切片

requestIdleCallback: 浏览器提供的Api,其注册的回调函数会在空闲阶段执行,但是具有 浏览器兼容性, 触发频率不稳定 等问题.

Scheduler库: 由React实现的功能更完备的requestIdleCallbackpolyfill,判断浏览器帧空闲时间,额外能判断任务优先级.

React 早期采用的是 requestAnimationFrame + postMessage 来实现

卡顿即掉帧

浏览器的页面是一帧一帧绘制出来的,帧率与设备刷新率保持一致.

假设60hz, 即1000ms刷新60次, 一帧16.6ms, 此时页面渲染是流畅的.

每一帧的开头, 浏览器需要进行 事件处理 UI渲染等高优先级操作, 之后是空闲阶段

一个完整的帧

  1. event事件
  2. timer定时器
  3. begin Frame 开始帧
  4. requestAnimationFrame (请求动画帧)
  5. Layout布局
  6. Pain绘制
  7. requestIdleCallback (Idle Peroid 空闲时间)

requestAnimationFrame 浏览器提供的Api,其注册是回调函数会在4.阶段专门执行.

requestIdleCallback 浏览器提供的Api,其注册是回调函数会在空闲阶段执行.

function sleep(duration) {
    for (var t = Date.now(); Date.now() - t <= duration;) { }
}
const fibers = [
    () => {
        console.log('第1个任务开始');
        sleep(5000);
        console.log('第1个任务结束');
    },
    () => {
        console.log('第2个任务开始');
        sleep(20);
        console.log('第2个任务结束');
    },
    () => {
        console.log('第3个任务开始');
        sleep(20);
        console.log('第3个任务结束');
    }
]
requestIdleCallback(workLoop);
function workLoop(deadline) {
    //因为一帧是16.6ms,浏览器执行完高优先级之后,如果还有时间,会执行workLoop,timeRemaining获取此帧剩下的时间
    console.log(`本帧的剩余时间是`, deadline.timeRemaining());
    //如果没有剩余时间了,就会跳出循环
    while (deadline.timeRemaining() > 1 && works.length > 0) {
        performUnitOfWork();
    }
    //如果还有剩余任务
    if (works.length > 0) {
        console.log(`只剩下${deadline.timeRemaining()}ms,不够了,等待浏览器下次空闲 的时候再帮我调用`,);
        requestIdleCallback(workLoop);
    }
}
function performUnitOfWork() {
    let work = works.shift();//取出任务数组中的第一个任务,并移除第一个任务
    work();
}

模拟 StackReconciler 与 FiberReconciler

StackReconciler

//我们有一个虚拟DOM
let element = (
    <div id="A1">
        <div id="B1">
            <div id="C1"></div>
            <div id="C2"></div>
        </div>
        <div id="B2"></div>
    </div>
)
//虚拟DOM
let vdom = {
    "type": "div",
    "key": "A1",
    "props": {
        "id": "A1",
        "children": [
            {
                "type": "div",
                "key": "B1",
                "props": {
                    "id": "B1",
                    "children": [
                        {
                            "type": "div",
                            "key": "C1",
                            "props": { "id": "C1" },
                        },
                        {
                            "type": "div",
                            "key": "C2",
                            "props": { "id": "C2" },
                        }
                    ]
                },
            },
            {
                "type": "div",
                "key": "B2",
                "props": { "id": "B2" },
            }
        ]
    },
}
//以前我们直接把vdom渲染成了真实DOM
function render(vdom, container) {
    //根据虚拟DOM生成真实DOM
    let dom = document.createElement(vdom.type);
    //把除children以外的属性拷贝到真实DOM上
    Object.keys(vdom.props).filter(key => key !== 'children').forEach(key => {
        dom[key] = vdom.props[key];
    });
    //把此虚拟DOM的子节点,也渲染到父节点真实DOM上
    if (Array.isArray(vdom.props.children)) {
        vdom.props.children.forEach(child => render(child, dom));
    }
    container.appendChild(dom);
}

FiberReconciler

//1.把虚拟DOM构建成fiber树
let A1 = { type: 'div', props: { id: 'A1' } };
let B1 = { type: 'div', props: { id: 'B1' }, return: A1 };
let B2 = { type: 'div', props: { id: 'B2' }, return: A1 };
let C1 = { type: 'div', props: { id: 'C1' }, return: B1 };
let C2 = { type: 'div', props: { id: 'C2' }, return: B1 };
//A1的第一个子节点B1
A1.child = B1;
//B1的弟弟是B2
B1.sibling = B2;
//B1的第一个子节点C1
B1.child = C1;
//C1的弟弟是C2
C1.sibling = C2;

//下一个工作单元
let nextUnitOfWork = null;
const hasTimeRemaining = () => Math.floor(Math.random() * 10) % 2 == 0;
//render工作循环
function workLoop() {
    debugger
    //工作循环每一次处理一个fiber,处理完以后可以暂停
    //如果有下一个任务并且有剩余的时间的话,执行下一个工作单元,也就是一个fiber
    while (nextUnitOfWork && hasTimeRemaining()) {
        //执行一个任务并返回下一个任务
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
    console.log('render阶段结束');
    //render阶段结束
}
function performUnitOfWork(fiber) {// A1
    let child = beginWork(fiber);
    //如果执行完A1之后,会返回A1的第一个子节点
    if (child) {
        return child;
    }
    //如果没有子节点
    while (fiber) {//如果没有子节点说明当前节点已经完成了渲染工作
        completeUnitOfWork(fiber);//可以结束此fiber的渲染了 
        if (fiber.sibling) {//如果它有弟弟就返回弟弟
            return fiber.sibling;
        }
        fiber = fiber.return;//如果没有弟弟让爸爸完成,然后找叔叔
    }
}
function beginWork(fiber) {
    console.log('beginWork', fiber.props.id);
    return fiber.child;//B1
}
function completeUnitOfWork(fiber) {
    console.log('completeUnitOfWork', fiber.props.id);
}
nextUnitOfWork = A1;
workLoop();

代数效应(未完成,待加深理解)

代数效应 是 函数式编程 中的一个概念,

通过使用 代数语法 将副作用从 函数式编程 抽离, 保存函数式编程纯粹,避免副作用且无需关注其实现。

使用代数效益的作用: __只需要关注纯粹业务逻辑,无需考虑副作用的管理.

例如:

代数效应 与 Hooks: 只需知道 useState 会保存并返回state, 无需关注state在Hook中是如何保存的.

Fiber的存在就是践行了代数效应

为什么React不使用 Generator 实现异步可中断 的代数效应

Fiber Principles: Contributing To Fiber

  1. Generator函数被调用时,需要调用者使用额外的语法

  2. Generator…………….

单一优先级任务的中断与继续,使用 Generator 可以实现 异步可中断更新

高优先级任务插队, 的情况则无法使用 Generator

Generator执行的中间状态是上下文关联的,所以计算y时无法复用之前已经计算出的x,需要重新计算。

如果通过全局变量保存之前执行的中间状态,又会引入新的复杂度。

基于这些原因,React没有采用Generator实现协调器。

感谢

感谢卡颂大佬的React技术揭秘


文章作者: 罗紫宇
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 罗紫宇 !
  目录