🎉干货满满,React设计原理(二):藏在源码里的两个圈🎉

简介: 🎉干货满满,React设计原理(二):藏在源码里的两个圈🎉

💎 第二座大山:链表结构和双缓存机制

上篇文章中讲述了几个容易给源码阅读造成困扰的几个fiber相关的变量名称,这篇我将介绍下Fiber架构的链表结构和双缓存机制。

上文提到,FiberNode扮演多种角色时,保存着不同的数据,所以FiberNode保存的数据比较复杂。

本文重点,讲解作为Fiber架构的一环时,保存的链状数据结构(同时也会捎带的讲解其他的一些属性),以及双缓存机制,

🚗 链表结构

Fiber tree由多个FiberNode节点组成的树状链表结构的数据。每个FiberNode的节点都有以下几个和Fiber架构相关的重要属性:

// 指向父节点
this.return = null;
// 指向第一个子节点
this.child = null;
// 指向右边兄弟节点
this.sibling = null;

虽然根据不同的节点类型(比如函数组件、类组件、普通元素等)数据结构会有所不同,但是它们都会使用这三个属性描述它与它们相邻节点的关系。

比如,有如下的代码:

function App() {
  const [name, setName] = useState("mmdctjj");
  const [count, setCount] = useState(0);
  return (
    <>
      <button
        onClick={() => {
          setName(name => name + 'l')
          setCount(count => count + 1)
        }}
      >
        {count}--{name}
      </button>
    </>
  );
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

它们的Fiber tree示意图如下:

实际的Fiber树状链表结构如下:

此时对应的是mounted阶段的初始状态,如果我们点击一次按钮,新的树状链状结构(对应updated阶段)如下:

对比两次的Fiber数据结构,从中我们可以得出结论:

  • 🔥 在函数组件对应的链表结构中,React每次将更新的内容渲染在页面之后,会将组件里的每个useState返回的状态记录在memoizedState下的baseState属性上,返回的dispatch方法有queue属性上,同时使用next属性指向下一个状态。直到最后一个状态时,nextnull。这是我们发现的第二条链状结构。

  • 🔥 另外我们还发现,button所在的fiber结构中,memoizedPropspendingProps属性上存在childrenonClick属性

  • 🔥 我们还发现,更新之后,每个fiber结构的alternate都指向了上次的自己。这其实是双缓存机制的实现,下面我们还会讲到。

如果我们将上面的函数组件替换为具有同样功能的类组件时(代码如下)

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      count: 0,
      name: "mmdctjj",
    };
  }
  render() {
    return (
      <>
        <button
          onClick={() =>
            this.setState({
              count: this.state.count + 1,
              name: this.state.name + "l",
            })
          }
        >
          {this.state.count}--{this.state.name}
        </button>
      </>
    );
  }
}

它的树状链表结构如下:

这里我们发现类组件和函数组件不一样的地方:

  • 🔥 类组件的fiber结构的memoizedState属性仅仅对应this.state的值,没有了想函数组件的第二条链表。

  • 🔥 类组件的fiber结构的updateQueue属性承载了组件的更新信息。这里的更新我们以后会详细讲到的。

总结下,React会为不同类型的Fiber tree节点创建不同的数据结构(略微不同的FiberNode类型),不同的数据结构更新方式也不一样。

除了上面说到的类组件和函数组件,还有FargementSuspense内置组件类型和一些别的情况下的特殊组件。

🚗 双缓存机制

上面提到,更新之后每个fiber节点的alternate属性都会指向上次的自己。其实这是React的一种优化策略。

React在运行时解析vnode,更新之后标记出更新前后变动的dom,然后渲染在页面中。如果每次都重新生成新的dom显然十分浪费资源。

所以React一方面会为每个dom绑定上次的状态,当发生变更时,快速比对,找出变动的地方。

另一方面,React还在内存中维护了一棵Fiber tree,变量名为workInProgress,用于快速切换。

源码中,所有带着workInProgressXxx的变量,都是指运行在内存中的对象。比如workInProgressHook

上篇文章中提到过,每个应用都会有唯一的FiberRootNode实例用来维护整个应用的状态和组件信息。它有个current属性用于指向渲染在页面中的fiber tree,而每个fiber节点alternate指向另一棵树中的自己。

接下来我们从组件开始加载到更新,看看双缓存机制的作用过程。

首先是应用被建立。App组件还未还未加载,此时是FiberRootNodecurrent属性为null

App组件解析成vMNode后,还在内存workInProgress中时:

当将vNode渲染在浏览器时,FiberRootNodecurrent属性指向workInProgressworkInProgress置空操作:

此时,我们点击button的点击事件,触发更新,内存中又多了个一棵树:

通过alternate属性比对,发现是App组件状态发生改变了,所以从App组件开始替换子树,然后将FiberRootNodecurrent属性指向workInProgress成为新的curent属性,旧的current替换之后成为workInProgress,并置为空,等待下次的更新:

这里我小小地剧透下,上述整个过程主要是render阶段地内容。具体而言,render阶段又可以分为三个小阶段:

  • beginWork阶段:顺着child属性向下遍历,找到变化地地方,打上标记
  • complateWork阶段:顺着return属性向上回归,将有标记的地方更新,此时就是更新workInProgress对应地Fiber tree
  • commitRoot阶段:将workInProgress对应的Fiber tree渲染到页面,同时完成上述指针的切换工作。

🚗 总结

React为不同的节点类型构建了不同的fiber结构和更新机制,但总的来说,它们具有同样的链表结构。

本文重点介绍了类组件和函数组件的一些字段区别。另外通过alternate引出并介绍了双缓存机制:currentworkInProgress的循环往替更新。

就是这两个重要的”圈“,给React套上了神秘的面纱。

🎉 最后

如果你发现本文一些错误的地方,请不吝指正,肥肠感谢🙏

这是本系列的第二篇了,真的干货满满,全文近六千五字符。

这个系列的目的通过分析一些理论知识,降低阅读源码的难度,即使不读源码也会对React的设计思想有总体上的理解。

  • 🎉干货满满,React设计原理(一):藏在源码里的紧箍咒,几个容易混淆的变量🎉
  • 🎉干货满满,React设计原理(二):藏在源码里的两个圈,关键的链表结构和双缓存技术🎉
  • 🎉干货满满,React设计原理(三):藏在源码里的排位赛,Lanu模型Batched Updates🎉写作...
  • 🎉干货满满,React设计原理(四):藏在源码里的传呼机,Dispatch机制和事件系统🎉写作中...
  • 🎉干货满满,React设计原理(五):藏在源码里的xx,待定🎉

所以对你有帮助话请给我点下赞,这对我很重要!

相关文章
|
7月前
|
算法 前端开发 JavaScript
React的diff算法原理
React的diff算法原理
133 0
|
7月前
|
JSON 缓存 前端开发
【React】React原理面试题集锦
本文集合一些React的原理面试题,方便读者以后面试查漏补缺。作者给出自认为可以让面试官满意的简易答案,如果想要了解更深刻,可以点击链接查看对应的详细博文。在此对链接中的博文作者非常感谢🙏。
147 0
|
2月前
|
存储 前端开发 测试技术
React Hooks 的工作原理
【10月更文挑战第1天】
|
2月前
|
移动开发 JSON 数据可视化
精选八款包括可视化CMS,jquery可视化表单,vue可视化拖拉,react可视化源码
精选八款包括可视化CMS,jquery可视化表单,vue可视化拖拉,react可视化源码
59 0
|
4月前
|
前端开发 算法 JavaScript
React原理之Diff算法
【8月更文挑战第24天】
|
4月前
|
前端开发 JavaScript 算法
如何学习react原理
【8月更文挑战第9天】 如何学习react原理
48 6
|
4月前
|
开发者 安全 UED
JSF事件监听器:解锁动态界面的秘密武器,你真的知道如何驾驭它吗?
【8月更文挑战第31天】在构建动态用户界面时,事件监听器是实现组件间通信和响应用户操作的关键机制。JavaServer Faces (JSF) 提供了完整的事件模型,通过自定义事件监听器扩展组件行为。本文详细介绍如何在 JSF 应用中创建和使用事件监听器,提升应用的交互性和响应能力。
41 0
|
4月前
|
前端开发 Java UED
瞬间变身高手!JSF 与 Ajax 强强联手,打造极致用户体验的富客户端应用,让你的应用焕然一新!
【8月更文挑战第31天】JavaServer Faces (JSF) 是 Java EE 标准的一部分,常用于构建企业级 Web 应用。传统 JSF 应用采用全页面刷新方式,可能影响用户体验。通过集成 Ajax 技术,可以显著提升应用的响应速度和交互性。本文详细介绍如何在 JSF 应用中使用 Ajax 构建富客户端应用,并通过具体示例展示 Ajax 在 JSF 中的应用。首先,确保安装 JDK 和支持 Java EE 的应用服务器(如 Apache Tomcat 或 WildFly)。
47 0
|
4月前
|
缓存 JavaScript 前端开发
【React生态进阶】React与Redux完美结合:从原理到实践全面解析构建大规模应用的最佳策略与技巧分享!
【8月更文挑战第31天】React 与 Redux 的结合解决了复杂状态管理的问题,提升了应用性能。本文详细介绍了在 React 应用中引入 Redux 的原因、步骤及最佳实践,包括安装配置、状态管理、性能优化等多方面内容,并提供了代码示例,帮助你构建高性能、易维护的大规模应用。
72 0
|
4月前
|
前端开发 JavaScript 中间件
【前端状态管理之道】React Context与Redux大对决:从原理到实践全面解析状态管理框架的选择与比较,帮你找到最适合的解决方案!
【8月更文挑战第31天】本文通过电子商务网站的具体案例,详细比较了React Context与Redux两种状态管理方案的优缺点。React Context作为轻量级API,适合小规模应用和少量状态共享,实现简单快捷。Redux则适用于大型复杂应用,具备严格的状态管理规则和丰富的社区支持,但配置较为繁琐。文章提供了两种方案的具体实现代码,并从适用场景、维护成本及社区支持三方面进行对比分析,帮助开发者根据项目需求选择最佳方案。
75 0