前端框架_React知识点精讲

当我们心情愉悦时,大脑会分泌一种叫内啡肽的神经递质,它能帮助我们减轻疼痛。

大家好,我是柒八九

今天,我们继续前端面试的知识点。我们来谈谈关于React知识点的相关知识点和具体的算法。

该系列的文章,大部分都是前面文章的知识点汇总,如果想具体了解相关内容,请移步相关系列,进行探讨。

如果,想了解该系列的文章,可以参考我们已经发布的文章。如下是往期文章。

文章list

  1. CSS重点概念精讲
  2. JS_基础知识点精讲
  3. 网络通信_知识点精讲
  4. JS_手写实现
  5. 前端工程化_知识点精讲

好了,天不早了,干点正事哇。

你能所学到的知识点

  1. Fiber 机制 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️
  2. Fiber 调和器 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️
  3. React 元素 VS 组件
  4. React-全局状态管理 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️
  5. 构建面向未来的前端架构 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️

Fiber 机制

React 是一个用于构建用户界面JavaScript 库。

它的核心跟踪组件状态的变化并将更新的状态投射到屏幕上。


React 中,我们把这个过程称为{调和| Reconciliation}。我们调用 setState 方法,框架会检查{状态|state}或{属性|props}是否发生了变化,并在用户界面上重新显示一个组件。

从渲染方法返回的{不可变|immutable}的React元素树通常被称为{虚拟DOM| Virtual DOM} 。这个术语有助于在早期向人们解释React,但它也造成了混乱,在React文档中已不再使用。在这篇文章中,我将坚持称它为{React元素树| Tree of React elements}。

除了React元素树,该框架有一棵内部实例树(组件、DOM节点等),用来保持状态


16版开始,React推出了一个新的内部实例树的实现,以及管理它的算法,代号为Fiber

调和过程中还有其他操作,如调用生命周期方法或更新ref所有这些操作在 Fiber 架构中都被统称为 {工作| Work}。

工作的类型通常取决于React元素的类型。例如,

  • 对于一个类组件React 需要创建一个实例,
  • 而对于一个函数组件,它不需要这样做。

如你所知,我们在 React 中有许多种类的元素。

  • 类组件(React.Component)
  • 函数组件
  • 宿主组件(DOM节点)
  • Portals (将子节点渲染成存在于父组件的DOM层次之外的DOM节点)

React 元素的类型是由 createElement 函数的第一个参数定义的。这个函数一般在render方法中使用,用于创建一个元素。而在React开发中,我们一般都使用JSX语法来定义元素(而JSXcreateElement的语法糖),JSX 标签的第一部分决定了React元素的类型。例如,

  • 以大写字母开头表示JSX标签是指一个React组件
  • 以小写字母开头表示宿主组件或者自定义组件

从 {React 元素| React Element} 到 {Fiber 节点| Fiber Node}

React 中的每个组件都是一个UI表示

这里是我们的 ClickCounter 组件的模板。

<button key="1" onClick={this.onClick}>
  更新数字
button>
<span key="2">
  {this.state.count}
span>

{React 元素| React Element}

一旦模板通过{JSX编译器| JSX Compiler},你最终会得到一堆React元素。–>这就是真正从 React 组件的渲染方法中返回的东西,而不是HTML

如果不需要使用 JSX语法,可以使用React.createElement
render方法中对 React.createElement的调用将创建这样的两个数据结构

[
    {
        $$typeof: Symbol(react.element),
        type: 'button',
        key: "1",
        props: {
            children: '更新数字',
            onClick: () => { ... }
        }
    },
    {
        $$typeof: Symbol(react.element),
        type: 'span',
        key: "2",
        props: {
            children: 0
        }
    }
]

你可以看到 React 给这些对象添加了$$typeof属性,可以标识它们是React元素。然后还有描述元素的属性 typekeyprops,这些值取自你传递给React.createElement函数的内容。


{Fiber 节点| Fiber Node}

调和过程中,从render方法返回的每个React元素的数据都被合并到Fiber树中。


React元素不同,fiber不会在每次渲染时重新创建。这些是{可变的数据结构| mutable data structures},持有组件状态和 DOM信息

每个React元素都被转换为相应类型的Fiber节点,描述需要完成的工作。

可以把fiber看作是一个数据结构,它代表了一些要做的工作,或者说,一个工作单位


Fiber的架构还提供了一种方便的方式来跟踪、安排、暂停和中止工作。

当一个React元素第一次被转换成一个Fiber节点时,React 使用该元素的数据在 createFiberFromTypeAndProps 函数中创建一个fiber

在随后的更新中,React重用Fiber节点,只是使用来自相应 React元素 的数据更新必要的属性。如果相应的React元素不再从渲染方法中返回,React可能还需要根据关键props在层次结构中移动节点或删除它。

因为React为每个React元素创建了一个fiber节点,由于我们有一个由元素组成的element 树,所以我们也将有一个由fiber节点组成的fiber树。在我们的示例应用程序中,它看起来像这样。

前端框架_React知识点精讲_第1张图片
所有的Fiber节点都是通过childsiblingreturn属性构建成链表连接起来的。


Current Tree 和 workInProgress Tree

在第一次渲染之后,React 最终会有一个 Fiber 树,它反映了用来渲染 UI 的应用程序的状态。这个树通常被称为{当前树| Current Tree}。


当React开始状态更新时,它建立了一个所谓的{workInProgress 树| workInProgress Tree},反映了未来将被刷新到屏幕上的状态。

所有的工作都在workInProgress树fiber 上进行。当React穿过current树时,对于每个现有fiber节点,它创建一个备用节点,构成 workInProgress树。这个节点是使用render方法返回的React元素的数据创建的。一旦更新处理完毕,所有相关的工作都完成了,React 就会有一个备用的树,准备刷新到屏幕上。一旦这个workInProgress树被渲染到屏幕上,它就成为current

React 的核心原则之一是一致性React 总是一次性地更新所有DOM–它不会显示部分结果workInProgress树作为一个用户不可见的{草稿|draft},这样 React 可以先处理所有的组件,然后将它们的变化刷新到屏幕上

每个fiber节点通过alternate属性保存着对另一棵树上的对应节点的引用。

current树的一个节点指向workInProgress树的节点,反之亦然。


{副作用| Side-effects}

可以把React中的组件看作是一个使用state和props来计算UI表现的函数

每一个操作,如DOM的突变调用生命周期方法,都应该被视为一个副作用,或者简单地说,是一个{效果|effect}。

从React组件中执行过数据获取事件订阅手动改变DOM。我们称这些操作为 “副作用”(或简称 “效果”),因为它们会影响其他组件,而且不能在渲染过程中进行。

你可以看到大多数stateprops的更新都会导致副作用的产生。由于应用效果是一种工作类型fiber节点是一种方便的机制,除了更新之外,还可以跟踪效果

每个fiber节点都可以有与之相关的效果。它们被编码在 effectTag 字段中。

所以Fiber中的效果基本上定义了更新处理后需要对实例进行的操作

  • 对于宿主组件(DOM元素),工作包括添加更新删除元素。
  • 对于类组件,React 可能需要更新Refs并调用 componentDidMountcomponentDidUpdate 生命周期方法。

{效果清单| Effects list}

React处理更新的速度非常快,为了达到这种性能水平,它采用了一些有趣的技术。其中之一是建立一个带有副作用的fiber节点的线性列表,以便快速迭代。迭代线性列表要比树形快得多,而且不需要在没有副作用的节点上花费时间。

这个列表的目的是标记有DOM更新或其他与之相关的副作用的节点。这个列表是 workInProgress 树的一个子集,并且使用 nextEffect 属性链接,而不是currentworkInProgress 树中使用的 child 属性。

React 应用想象成一棵圣诞树,用 "圣诞灯 "把所有有效果的节点绑在一起

当访问这些节点时,React 使用 firstEffect 指针来计算列表的开始位置,用 nextEffect将拥有效果的节点连接起来。 所以上图可以表示为这样的一个线性列表。

前端框架_React知识点精讲_第2张图片


Fiber-Node的数据结构

现在让我们来看看为 ClickCounter 组件创建的fiber节点的结构。

{
    stateNode: new ClickCounter,
    type: ClickCounter,
    alternate: null,
    key: null,
    updateQueue: null,
    memoizedState: {count: 0},
    pendingProps: {},
    memoizedProps: {},
    tag: 1,
    effectTag: 0,
    nextEffect: null
}
  1. stateNode
    • 保存对与fiber节点相关的组件、DOM节点或其他React元素类型的类实例的引用
  2. type
    • 定义了与该fiber相关的函数或类
  3. tag
    • 定义了fiber的类型。
      定义在调和算法中被用来确定需要做什么工作
  4. updateQueue
    • 状态更新、回调和DOM更新的队列
  5. memoizedState
    • 用于创建输出的fiber的state
    • 当处理更新时,它反映了当前屏幕上呈现的状态。
  6. memoizedProps
    • 上一次渲染过程中用于创建输出的 fiberprops
  7. pendingProps
    • 从React元素的新数据中更新的props,需要应用于子组件或DOM元素。
  8. key
    • 用于在一组子item唯一标识子项的字段。

渲染算法

React的工作主要分两个阶段进行:{渲染| Render}和{提交| Commit}。

render阶段,React 通过 setStateReact.render对预定的组件进行更新,并找出UI中需要更新的内容。

  • 如果是初次渲染Reactrender方法返回的每个元素创建一个新的fiber节点
  • 在接下来的更新中,现有 React元素fiber重新使用和更新

该阶段的结果是一棵标有副作用的fiber节点树。这些效果描述了在接下来的提交阶段需要做的工作。在commit阶段,React 遍历标有效果的fiber树,并将效果应用于实例。它遍历effect列表,执行DOM更新和其他用户可见的变化。

重要的是,render阶段的工作可以异步进行React 可以根据可用的时间来处理一个或多个fiber节点,然后停下来,把已经完成的工作储存起来,并将处理fiber的操作{暂停|yield}。然后从上次离开的地方继续。但有时,可能需要丢弃已完成的工作并从头开始。针对在这个阶段执行的工作的暂停操作不会导致任何用户可见的UI变化,如DOM更新。相比之下,接下来的提交阶段总是同步的。这是因为在这个阶段进行的工作会导致用户可见的变化,例如DOM更新。这就是为什么React需要一次性完成这些工作。

调用生命周期的方法是React执行的一种工作类型。有些方法是在render阶段调用的,有些是在commit阶段调用的。下面是在render阶段工作时调用的生命周期的列表

  • [UNSAFE_]componentWillMount (废弃)
  • [UNSAFE_]componentWillReceiveProps (废弃)
  • static getDerivedStateFromProps
  • shouldComponentUpdate
  • [UNSAFE_]componentWillUpdate (废弃)
  • render

正如你所看到的,从16.3版本开始,一些在渲染阶段执行的传统生命周期方法被标记为 UNSAFE。它们现在在文档中被称为遗留生命周期。它们将在未来的16.x版本中被废弃。

我们来简单解释下,为什么会有生命周期会被遗弃。

由于render阶段不会产生像DOM更新那样的副作用,React可以异步处理组件的更新(甚至有可能在多个线程中进行)。然而,标有 UNSAFE 的生命周期经常被滥用。开发者倾向于将有副作用的代码放在这些方法中,这可能会给新的异步渲染方法带来问题

下面是在commit阶段执行的生命周期方法的列表。

  • getSnapshotBeforeUpdate
  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

因为这些方法在同步提交阶段执行,它们可能包含副作用并触及DOM。

这里我们贴一个针对react-16.4+版本的类组件的生命周期方法。

前端框架_React知识点精讲_第3张图片


Render 阶段

调和算法总是使用 renderRoot 函数从最上面的 HostRoot fiber节点开始。然而,React会跳过已经处理过的fiber节点,直到找到工作未完成的节点

例如,如果你在组件树的深处调用 setState,React会从顶部开始,但迅速跳过父节点,直到它到达调用了setState方法的组件。

workLoop 主要流程

所有fiber节点都在 workLoop 中被处理

下面是该循环的同步部分的实现。

function workLoop(isYieldy) {
  if (!isYieldy) {
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {...}
}

在上面的代码中,nextUnitOfWork 持有对来自 workInProgress 树fiber节点的引用,该节点有一些工作要做。 当 React 遍历 Fiber 树时,它使用这个变量来了解是否还有其他未完成工作的 Fiber 节点。 处理current fiber后,该变量将包含对树中下一个fiber节点的引用或为空。

有 4 个主要函数用于遍历树并启动或完成工作:

  1. performUnitOfWork
  2. beginWork
  3. completeUnitOfWork
  4. completeWork

React 沿着树向下移动时。它先完成孩子节点的处理,再转向其父节点

从代码实现中可以看出,performUnitOfWorkcompleteUnitOfWork 都主要用于迭代,而主要操作发生在 beginWorkcompleteWork 函数中


Commit 阶段

该阶段从函数 completeRoot 开始。 这是 React 更新 DOM 并调用变动前后生命周期方法的地方。

React 进入这个阶段时,它有 2 棵树

  • 第一个树代表当前在屏幕上呈现的状态。
  • 第二个树是在render阶段构建了一个{备用树| alternate tree}。
    • 它在源代码中称为 finishedWorkworkInProgress,表示需要在屏幕上反映的状态。
    • 该备用树通过child指针和sibling指针进行各个节点的连接。

还有一个效果列表——来自finishedWork树的节点子集,通过 nextEffect 指针链接。 请记住,效果列表是render阶段的结果。 渲染的重点是确定哪些节点需要插入、更新或删除,哪些组件需要调用其生命周期方法。 这就是效果列表告诉我们。 它正是在commit阶段需要处理的节点集

commit阶段运行的主要函数是 commitRoot。 在指定的fiber上执行更新操作。

以下是运行上述步骤的函数的要点:

function commitRoot(root, finishedWork) {
    commitBeforeMutationLifecycles()
    commitAllHostEffects();
    root.current = finishedWork;
    commitAllLifeCycles();
}

这些子函数中的每一个都实现了一个循环,该循环遍历效果列表并检查效果的类型。 当它找到与函数目的相关的效果时,它会应用它。


  1. 突变前的生命周期
    • 对于类组件,此效果意味着调用 getSnapshotBeforeUpdate 生命周期方法。
  2. DOM更新
    • commitAllHostEffectsReact 执行 DOM 更新的函数。
  3. 突变后的生命周期方法
    • commitAllLifecyclesReact 调用所有剩余生命周期方法 componentDidUpdatecomponentDidMount 的函数。

Fiber 调和器

{Fiber 调和器| Fiber Reconciler}成为 React 16+版本的默认调和器,它完全重写了 React 原有的调和算法,以解决 React 中一些长期存在的问题。

这一变化使 React 摆脱了{同步堆栈调节器| Synchronous Stack Reconciler}的限制。以前,你可以添加或删除组件,但必须等调用堆栈为空,而且任务不能被中断

使用新的调节器,也确保最重要的更新尽快发生。(更新存在优先级)

{堆栈调和器| Stack Reconciler}

为什么这被称为 "堆栈 "调节器?这个名字来自于 "堆栈 "数据结构,它是一个后进先出的机制。

我们从最熟悉的ReactDOM.render(, document.getElementById('root'))语法开始探索。

ReactDOM 模块将传递给调和器,但这里有两个问题:

  • 指的是什么?
  • 什么是调和器?

让我们来一一解答这些问题。

指的是什么?

是一个React元素。根据 React博客描述,”元素是一个描述组件实例DOM节点及其所需属性的普通对象“。

换句话说,元素不是实际的DOM节点或组件实例;它们是一种向 React 描述它们是什么类型的元素,它们拥有什么属性,以及它们的孩子是谁的信息组织方式。

React 元素在早期的React介绍文档中,有另外一个家喻户晓的名字: {虚拟DOM| Virtual-DOM}

只不过,V-Dom在理解上在某些场景下会产生歧义,所以逐渐被React 元素所替代


React {调和算法| Reconciliation}

该算法使得 React 更容易解析和遍历应用,用以建立对应的DOM树实际的渲染工作会在遍历完成后发生

React 遇到一个类或一个函数组件时,它会基于元素的props来渲染UI视图。

例如,如果组件渲染了以下内容,那么 React 会遍历

你可能感兴趣的:(javascript,前端,react.js,前端框架)