从0到一实现React Fiber从零到一实现React Fiber

为什么引入fiber

JavaScript引擎和页面渲染引擎两个线程是互斥的,当其中一个线程执行时,另一个线程只能挂起等待 如果 JavaScript线程长时间地占用了主线程,那么渲染层面的更新就不得不长时间地等待,界面长时间不更新,会导致页面响应度变差,用户可能会感觉到卡顿 而这也正是React 15 的 Stack Reconciler 所面临的问题,当React在渲染组件时,从开始到渲染完成整个过程是一气呵成的,无法中断 如果组件较大,那么js线程会一直执行,然后等到整棵VDOM树计算完成后,才会交给渲染的线程 这就会导致一些用户交互、动画等任务无法立即得到处理,导致卡顿的情况,

JavaScript执行Javascript引擎和页面渲染在同一个线程中,GUI渲染和Javascript执行两者之间是互斥的 工 如果某个任务执行时间过长,浏览器就会推迟渲染。这就引入了Fiber

React Fiber是React 16引入的一种新的协调引擎,它的目标是使React能够更好地处理大型应用和动态更新。Fiber的主要目标是实现以下几个方面的改进:

  1. 增量渲染:将渲染工作分成多个小任务,避免长时间占用主线程,从而提高应用的响应性。
  1. 可中断和恢复:在渲染过程中,可以中断当前的渲染任务,并在稍后恢复,这样可以更好地利用浏览器的主线程。
  1. 优先级调度:根据任务的优先级来调度渲染工作,确保高优先级的任务(如用户交互)能够优先执行。
  1. 并发模式:支持并发模式,使得React可以同时处理多个更新任务,提高渲染效率。

Fiber执行阶段

每次渲染有两个阶段:Reconciliation(协调render阶段)和Commit(提交阶段) - 协调的阶段:可以认为是Diff阶段,这个阶段可以被终止,这个阶段会找出所有节点变更,例如节点新增、删除、属性变更等等,这些变更React称之为副作用。 - 提交阶段:将上一阶段计算出来的需要处理的副作用(effects)一次性执行了。这个阶段必须同步执行,不能被打断。

Fiber的核心概念

Fiber是React中用于表示组件树的数据结构,每个Fiber节点对应一个React组件实例或DOM节点。Fiber节点包含以下关键属性:

  • type :组件的类型(类组件或函数组件)。
  • key :组件的唯一标识。
  • props :组件的属性。
  • stateNode :组件对应的DOM节点或组件实例。
  • return :父Fiber节点。
  • child :第一个子Fiber节点。
  • sibling :下一个兄弟Fiber节点。
  • alternate :当前Fiber节点的旧版本(用于并发模式)。
  • Fiber树在执行时采用的是链表结构,存放指针如上几个,具备了可以随机存取的特性,而且时间复杂度很低,当中断或者优先调度某个Fiber的时候都能很快的找到他的父节点和子节点和兄弟节点,完整渲染的时候是按照树的深度优先遍历的。

手写简单的Render函数

下面是一个简化的React Fiber渲染过程的实现,包括创建Fiber节点和基本的协调过程:

复制代码
// 定义Fiber节点
class Fiber {
  constructor(type, key, props, returnFiber) {
    this.type = type;
    this.key = key;
    this.props = props;
    this.return = returnFiber;
    this.child = null;
    this.sibling = null;
    this.alternate = null;
    this.stateNode = null;
  }
}
// 创建Fiber树的函数
function createFiberTree(element, returnFiber) {
  const fiber = new Fiber(element.type, element.key, element.props, returnFiber);
  if (typeof element.type === 'function') {
    // 如果是函数组件,调用它并获取子元素
    const children = element.type(element.props);
    reconcileChildren(fiber, children);
  } else {
    // 如果是DOM元素,设置stateNode
    fiber.stateNode = createDom(fiber);
  }
  return fiber;
}
// 创建DOM节点的函数
function createDom(fiber) {
  const dom =
    fiber.type === 'TEXT_ELEMENT'
      ? document.createTextNode('')
      : document.createElement(fiber.type);
  updateDom(dom, {}, fiber.props);
  return dom;
}
// 更新DOM的函数
const isEvent = key => key.startsWith('on');
const isProperty = key =>
  key !== 'children' && !isEvent(key) && key !==  'key';
function updateDom(dom, prevProps, nextProps) {
  // 移除旧的属性
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(name => !(name in nextProps))
    .forEach(name => {
      dom[name] = '';
    });
  // 设置新的属性
  Object.keys(nextProps)
    .filter(isProperty)
    .forEach(name => {
      dom[name] = nextProps[name];
    });
  // 移除旧的事件监听器
  Object.keys(prevProps)
    .filter(isEvent)
    .forEach(name => {
      const eventType = name
        .toLowerCase()
        .substring(2);
      dom.removeEventListener(eventType, prevProps[name]);
    });
  // 添加新的事件监听器
  Object.keys(nextProps)
    .filter(isEvent)
    .forEach(name => {
      const eventType = name
        .toLowerCase()
        .substring(2);
      dom.addEventListener(eventType, nextProps[name]);
    });
}
// 协调子元素的函数
function reconcileChildren(wipFiber, elements) {
  let index = 0;
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
  let prevSibling = null;
  while (index < elements.length || oldFiber != null) {
    const element = elements[index];
    let newFiber = null;
    const sameType = oldFiber && element && element.type === oldFiber.type;
    if (sameType) {
      newFiber = {
        type: oldFiber.type,
        key: oldFiber.key,
        props: element.props,
        return: wipFiber,
        stateNode: oldFiber.stateNode,
        alternate: oldFiber,
      };
      newFiber.child = oldFiber.child;
      newFiber.sibling = oldFiber.sibling;
      oldFiber = oldFiber.sibling;
    } else if (element) {
      newFiber = createFiberTree(element, wipFiber);
    } else if (oldFiber) {
      oldFiber.return.child = null;
    }
    if (oldFiber === null) {
      if (newFiber != null) {
        if (prevSibling == null) {
          wipFiber.child = newFiber;
        } else {
          prevSibling.sibling = newFiber;
        }
      }
    } else if (newFiber == null) {
      if (prevSibling == null) {
        wipFiber.child = null;
      } else {
        prevSibling.sibling = null;
      }
    }
    prevSibling = newFiber;
    index++;
  }
}
// 简单的render函数
function render(element, container) {
  const rootFiber = createFiberTree(element, null );
  const dom = rootFiber.stateNode;
  container.appendChild(dom);
}
// 示例使用
const element = {
  type: 'div',
  props: {
    children: 'Hello, Fiber!',
  },
};
render(element, document.getElementById('root'));

总结

以上代码实现了一个简化的React Fiber渲染过程,包括创建Fiber节点、协调子元素和更新DOM。实际的React Fiber实现要复杂得多,涉及到更多的优化和功能,如优先级调度、并发模式等。通过这个简化的示例,可以更好地理解iber的基本概念和工作原理。

相关推荐
皮实的芒果几秒前
前端实时通信方案对比:WebSocket vs SSE vs setInterval 轮询
前端·javascript·性能优化
鹿九巫几秒前
【CSS】层叠,优先级与继承(三):超详细继承知识点
前端·css
奕云1 分钟前
react-redux源码分析
前端
咸鱼一号机2 分钟前
:global 是什么
前端
专业掘金3 分钟前
0425 手打基础丸
前端
五号厂房3 分钟前
Umi Max 如何灵活 配置多环境变量
前端
红尘散仙6 分钟前
六、WebGPU 基础入门——Vertex 缓冲区和 Index 缓冲区
前端·rust·gpu
南望无一6 分钟前
webpack性能优化和构建优化
前端·webpack
il6 分钟前
Deepdive into Tanstack Query - 2.0 Query Core 概览
前端·javascript
Shawn5908 分钟前
前端时间管理实践:从时间标准化到工程化封装
前端·javascript