当前位置: 首页 > article >正文

从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的基本概念和工作原理。


http://www.kler.cn/a/563788.html

相关文章:

  • 【ESP32S3接入讯飞在线语音识别】
  • 大语言加持的闭环端到端自动驾驶模型 学习笔记纯干货
  • 【C++设计模式】 单例设计模式:重要常用却并非完美之选
  • Cell:临床机器学习在癌症诊断、预后和治疗方面的进展
  • DIALOGPT:大规模生成式预训练用于对话响应生成
  • 奇安信率先使用DeepSeek落地金融行业了
  • ffmpeg avformat_open_input的作用
  • Java+SpringBoot+Vue+数据可视化的音乐推荐与可视化平台(程序+论文+讲解+安装+调试+售后)
  • 新版本的idea用不习惯,怎么还原为之前版本的UI界面?idea界面还原,idea新版本ui设置
  • Qt开发⑦Qt的窗口_上_菜单栏+工具栏+状态栏
  • 剖析IO原理和零拷贝机制
  • Pi-hole v6释出
  • 表单验证和正则表达式
  • js获取后端Long类型返回json时number类型精度丢失
  • 机器人替代人工上下料,已成为工业领域的必然趋势
  • 单细胞肿瘤细胞识别机器学习研究
  • 【Git 学习笔记_27】DIY 实战篇:利用 DeepSeek 实现 GitHub 的 GPG 密钥创建与配置
  • 深度学习笔记数学方面——矩阵计算,自动求导
  • Linux关机重启和登录注销
  • C语言实现单链表