React Hydrate原理源码解析

目录
  • 引言
  • Demo
  • ReactDOM.render
  • ReactDOM.hydrate
    • hydrate 过程
    • 事件绑定
  • hydrate 源码剖析
  • beginWork
    • HostRoot Fiber
    • HostComponent
    • HostText Fiber
    • tryToClaimNextHydratableInstance
  • completeUnitOfWork
    • popHydrationState
    • prepareToHydrateHostInstance
    • prepareToHydrateHostTextInstance
    • prepareToHydrateHostTextInstance

引言

React 渲染过程,即ReactDOM.render执行过程分为两个大的阶段:render 阶段以及 commit 阶段。React.hydrate渲染过程和ReactDOM.render差不多,两者之间最大的区别就是,ReactDOM.hydraterender 阶段,会尝试复用(hydrate)浏览器现有的 dom 节点,并相互关联 dom 实例和 fiber,以及找出 dom 属性和 fiber 属性之间的差异。

Demo

这里,我们在 index.html 中直接返回一段 html,以模拟服务端渲染生成的 html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Mini React</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <div id="root"><div id="root"><div id="container"><h1 id="A">1<div id="A2">A2</div></h1><p id="B"><span id="B1">B1</span></p><span id="C">C</span></div></div></div>
  </body>
</html>

注意,root 里面的内容不能换行,不然客户端hydrate的时候会提示服务端和客户端的模版不一致。

新建 index.jsx:

import React from "react";
import ReactDOM from "react-dom";
class Home extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 1,
    };
  }
  render() {
    const { count } = this.state;
    return (
      <div id="container">
        <div id="A">
          {count}          <div id="A2">A2</div>
        </div>
        <p id="B">
          <span id="B1">B1</span>
        </p>
      </div>
    );
  }
}
ReactDOM.hydrate(<Home />, document.getElementById("root"));

对比服务端和客户端的内容可知,服务端h1#A和客户端的div#A不同,同时服务端比客户端多了一个span#C

在客户端开始执行之前,即 ReactDOM.hydrate 开始执行前,由于服务端已经返回了 html 内容,浏览器会立马显示内容。对应的真实 DOM 树如下:

注意,这不是 fiber 树!!

ReactDOM.render

先来回顾一下 React 渲染更新过程,分为两大阶段,五小阶段:

render 阶段

  • beginWork
  • completeUnitOfWork

commit 阶段。

  • commitBeforeMutationEffects
  • commitMutationEffects
  • commitLayoutEffects

React 在 render 阶段会根据新的 element tree 构建 workInProgress 树,收集具有副作用的 fiber 节点,构建副作用链表。

特别是,当我们调用ReactDOM.render函数在客户端进行第一次渲染时,render阶段的completeUnitOfWork函数针对HostComponent以及HostText类型的 fiber 执行以下 dom 相关的操作:

  • 调用document.createElementHostComponent类型的 fiber 节点创建真实的 DOM 实例。或者调用document.createTextNodeHostText类型的 fiber 节点创建真实的 DOM 实例
  • 将 fiber 节点关联到真实 dom 的__reactFiber$rsdw3t27flk(后面是随机数)属性上。
  • 将 fiber 节点的pendingProps 属性关联到真实 dom 的__reactProps$rsdw3t27flk(后面是随机数)属性上
  • 将真实的 dom 实例关联到fiber.stateNode属性上:fiber.stateNode = dom
  • 遍历 pendingProps,给真实的dom设置属性,比如设置 id、textContent 等

React 渲染更新完成后,React 会为每个真实的 dom 实例挂载两个私有的属性:__reactFiber$__reactProps$,以div#container为例:

ReactDOM.hydrate

hydrate中文意思是水合物,这样理解有点抽象。根据源码,我更乐意将hydrate的过程描述为:React 在 render 阶段,构造 workInProgress 树时,同时按相同的顺序遍历真实的 DOM 树,判断当前的 workInProgress fiber 节点和同一位置的 dom 实例是否满足hydrate的条件,如果满足,则直接复用当前位置的 DOM 实例,并相互关联 workInProgress fiber 节点和真实的 dom 实例,比如:

fiber.stateNode = dom;
dom.__reactProps$ = fiber.pendingProps;
dom.__reactFiber$ = fiber;

如果 fiber 和 dom 满足hydrate的条件,则还需要找出dom.attributesfiber.pendingProps之间的属性差异。

遍历真实 DOM 树的顺序和构建 workInProgress 树的顺序是一致的。都是深度优先遍历,先遍历当前节点的子节点,子节点都遍历完了以后,再遍历当前节点的兄弟节点。因为只有按相同的顺序,fiber 树同一位置的 fiber 节点和 dom 树同一位置的 dom 节点才能保持一致

只有类型为HostComponent或者HostText类型的 fiber 节点才能hydrate。这一点也很好理解,React 在 commit 阶段,也就只有这两个类型的 fiber 节点才需要执行 dom 操作。

fiber 节点和 dom 实例是否满足hydrate的条件:

  • 对于类型为HostComponent的 fiber 节点,如果当前位置对应的 DOM 实例nodeTypeELEMENT_NODE,并且fiber.type === dom.nodeName,那么当前的 fiber 可以混合(hydrate)
  • 对于类型为HostText的 fiber 节点,如果当前位置对应的 DOM 实例nodeTypeTEXT_NODE,同时fiber.pendingProps不为空,那么当前的 fiber 可以混合(hydrate)

hydrate的终极目标就是,在构造 workInProgress 树的过程中,尽可能的复用当前浏览器已经存在的 DOM 实例以及 DOM 上的属性,这样就无需再为 fiber 节点创建 DOM 实例,同时对比现有的 DOM 的attribute以及 fiber 的pendingProps,找出差异的属性。然后将 dom 实例和 fiber 节点相互关联(通过 dom 实例的__reactFiber$以及__reactProps$,fiber 的 stateNode 相互关联)

hydrate 过程

React 在 render 阶段构造HostComponent或者HostText类型的 fiber 节点时,会首先调用 tryToClaimNextHydratableInstance(workInProgress) 方法尝试给当前 fiber 混合(hydrate)DOM 实例。如果当前 fiber 不能被混合,那当前节点的所有子节点在后续的 render 过程中都不再进行hydrate,而是直接创建 dom 实例。等到当前节点所有子节点都调用completeUnitOfWork完成工作后,又会从当前节点的兄弟节点开始尝试混合。

以下面的 demo 为例

// 服务端返回的DOM结构,这里为了直观,我格式化了一下,按理服务端返回的内容,是不允许换行或者有空字符串的
<body>
  <div id="root">
    <div id="container">
      <h1 id="A">
        1        <div id="A2">A2</div>
      </h1>
      <p id="B">
        <span id="B1">B1</span>
      </p>
      <span id="C">C</span>
    </div>
  </div>
</body>
// 客户端生成的内容
<div id="container">
  <div id="A">
    1    <div id="A2">A2</div>
  </div>
  <p id="B">
    <span id="B1">B1</span>
  </p>
</div>

render 阶段,按以下顺序:

div#container 满足hydrate的条件,因此关联 dom,fiber.stateNode = div#container。然后使用hydrationParentFiber记录当前混合的 fiber 节点:hydrationParentFiber = fiber。获取下一个 DOM 实例,这里是h1#A,保存在变量nextHydratableInstance中,nextHydratableInstance = h1#A

这里,hydrationParentFibernextHydratableInstance 都是全局变量。

  • div#Ah1#A 不能混合,这时并不会立即结束混合的过程,React 继续对比h1#A的兄弟节点,即p#B,发现div#A还是不能和p#B混合,经过最多两次对比,React 认为 dom 树中已经没有 dom 实例满足和div#A这个 fiber 混合的条件,于是div#A节点及其所有子孙节点都不再进行混合的过程,此时将isHydrating设置为 false 表明div#A这棵子树都不再走混合的过程,直接走创建 dom 实例。同时控制台提示:Expected server HTML to contain a matching.. 之类的错误。
  • beginWork 执行到文本节点 1 时,发现 isHydrating = false,因此直接跳过混合的过程,在completeUnitOfWork阶段直接调用document.createTextNode直接为其创建文本节点
  • 同样的,beginWork 执行到节点div#A2时,发现isHydrating = false,因此直接跳过混合的过程,在completeUnitOfWork阶段直接调用document.createElement直接为其创建真实 dom 实例,并设置属性
  • 由于div#A的子节点都已经completeUnitWork了,轮到div#A调用completeUnitWork完成工作,将hydrationParentFiber指向其父节点,即div#container这个 dom 实例。设置isHydrating = true表明可以为当前节点的兄弟节点继续混合的过程了。div#A没有混合的 dom 实例,因此调用document.createElement为其创建真实的 dom 实例。
  • p#B执行 beginWork。由于nextHydratableInstance保存的还是h1#Adom 实例,因此p#Bh1#A对比发现不能复用,React 尝试和h1#A的兄弟节点p#B对比,发现 fiberp#B和 domp#B能混,因此将h1#A标记为删除,同时关联 dom 实例:fiber.stateNode = p#B,保存hydrationParentFiber = fibernextHydratableInstance指向p#B的第一个子节点,即span#B1

...省略了后续的过程。

从上面的执行过程可以看出,hydrate 的过程如下:

  • 调用 tryToClaimNextHydratableInstance 开始混合
  • 判断当前 fiber 节点和同一位置的 dom 实例是否满足混合的条件。
  • 如果当前位置的 dom 实例不满足混合条件,则继续比较当前 dom 的兄弟元素,如果兄弟元素和当前的 fiber 也不能混合,则当前 fiber 及其所有子孙节点都不能混合,后续 render 过程将会跳过混合。直到当前 fiber 节点的兄弟节点 render,才会继续混合的过程。

事件绑定

React在初次渲染时,不论是ReactDOM.render还是ReactDOM.hydrate,会调用createRootImpl函数创建fiber的容器,在这个函数中调用listenToAllSupportedEvents注册所有原生的事件。

function createRootImpl(container, tag, options) {
  // ...
  var root = createContainer(container, tag, hydrate);
  // ...
  listenToAllSupportedEvents(container);
  // ...
  return root;
}

这里container就是div#root节点。listenToAllSupportedEvents会给div#root节点注册浏览器支持的所有原生事件,比如onclick等。React合成事件一文介绍过,React采用的是事件委托的机制,将所有事件代理到div#root节点上。以下面的为例:

<div id="A" onClick={this.handleClick}>
button
<div>

我们知道React在渲染时,会将fiber的props关联到真实的dom的__reactProps$属性上,此时

div#A.__reactProps$ = {
  onClick: this.handleClick
}

当我们点击按钮时,会触发div#root上的事件监听器:

function onclick(e){
  const target = e.target
  const fiberProps = target.__reactProps$
  const clickhandle = fiberProps.onClick
  if(clickhandle){
    clickhandle(e)
  }
}

这样我们就可以实现事件的委托。这其中最重要的就是将fiber的props挂载到真实的dom实例的__reactProps$属性上。因此,只要我们在hydrate阶段能够成功关联dom和fiber,就自然也实现了事件的“绑定”

hydrate 源码剖析

hydrate 的过程发生在 render 阶段,commit 阶段几乎没有和 hydrate 相关的逻辑。render 阶段又分为两个小阶段:beginWorkcompleteUnitOfWork。只有HostRootHostComponentHostText三种类型的 fiber 节点才需要 hydrate,因此源码只针对这三种类型的 fiber 节点剖析

beginWork

beginWork 阶段判断 fiber 和 dom 实例是否满足混合的条件,如果满足,则为 fiber 关联 dom 实例:fiber.stateNode = dom

function beginWork(current, workInProgress, renderLanes) {
  switch (workInProgress.tag) {
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
    case HostText:
      return updateHostText(current, workInProgress);
  }
}

HostRoot Fiber

HostRoot fiber 是容器root的 fiber 节点。

这里主要是判断当前 render 是ReactDOM.render还是ReactDOM.hydrate,我们调用ReactDOM.hydrate渲染时,root.hydrate为 true。

如果是调用的ReactDOM.hydrate,则调用enterHydrationState函数进入hydrate的过程。这个函数主要是初始化几个全局变量:

  • isHydrating。表示当前正处于 hydrate 的过程。如果当前节点及其所有子孙节点都不满足 hydrate 的条件时,这个变量为 false
  • hydrationParentFiber。当前混合的 fiber。正常情况下,该变量和HostComponent或者HostText类型的 workInProgress 一致。
  • nextHydratableInstance。下一个可以混合的 dom 实例。当前 dom 实例的第一个子元素或者兄弟元素。

注意getNextHydratable会判断 dom 实例是否是ELEMENT_NODE类型(对应的 fiber 类型是HostComponent)或者TEXT_NODE类型(对应的 fiber 类型是HostText)。只有ELEMENT_NODE或者HostText类型的 dom 实例才是可以 hydrate 的

function updateHostRoot(current, workInProgress, renderLanes) {
  if (root.hydrate && enterHydrationState(workInProgress)) {
    var child = mountChildFibers(workInProgress, null, nextChildren);
  }
  return workInProgress.child;
}
function getNextHydratable(node) {
  // 跳过 non-hydratable 节点.
  for (; node != null; node = node.nextSibling) {
    var nodeType = node.nodeType;
    if (nodeType === ELEMENT_NODE || nodeType === TEXT_NODE) {
      break;
    }
  }
  return node;
}
function enterHydrationState() {
  var parentInstance = fiber.stateNode.containerInfo;
  nextHydratableInstance = getNextHydratable(parentInstance.firstChild);
  hydrationParentFiber = fiber;
  isHydrating = true;
}

HostComponent

function updateHostComponent(current, workInProgress, renderLanes) {
  if (current === null) {
    tryToClaimNextHydratableInstance(workInProgress);
  }
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

HostText Fiber

function updateHostText(current, workInProgress) {
  if (current === null) {
    tryToClaimNextHydratableInstance(workInProgress);
  }
  return null;
}

tryToClaimNextHydratableInstance

假设当前 fiberA 对应位置的 dom 为 domA,tryToClaimNextHydratableInstance 会首先调用tryHydrate判断 fiberA 和 domA 是否满足混合的条件:

如果 fiberA 和 domA 满足混合的条件,则将hydrationParentFiber = fiberA;。并且获取 domA 的第一个子元素赋值给nextHydratableInstance

如果 fiberA 和 domA 不满足混合的条件,则获取 domA 的兄弟节点,即 domB,调用tryHydrate判断 fiberA 和 domB 是否满足混合条件:

  • 如果 domB 满足和 fiberA 混合的条件,则将 domA 标记为删除,并获取 domB 的第一个子元素赋值给nextHydratableInstance
  • 如果 domB 不满足和 fiberA 混合的条件,则调用insertNonHydratedInstance提示错误:"Warning: Expected server HTML to contain a matching",同时将isHydrating标记为 false 退出。

这里可以看出,tryToClaimNextHydratableInstance最多比较两个 dom 节点,如果两个 dom 节点都无法满足和 fiberA 混合的条件,则说明当前 fiberA 及其所有的子孙节点都无需再进行混合的过程,因此将isHydrating标记为 false。等到当前 fiberA 节点及其子节点都完成了工作,即都执行了completeWorkisHydrating才会被设置为 true,以便继续比较 fiberA 的兄弟节点

这里还需要注意一点,如果两个 dom 都无法满足和 fiberA 混合,那么nextHydratableInstance依然保存的是 domA,domA 会继续和 fiberA 的兄弟节点比对。

function tryToClaimNextHydratableInstance(fiber) {
  if (!isHydrating) {
    return;
  }
  var nextInstance = nextHydratableInstance;
  var firstAttemptedInstance = nextInstance;
  if (!tryHydrate(fiber, nextInstance)) {
    // 如果第一次调用tryHydrate发现当前fiber和dom不满足hydrate的条件,则获取dom的兄弟节点
    // 然后调用 tryHydrate 继续对比fiber和兄弟节点是否满足混合
    nextInstance = getNextHydratableSibling(firstAttemptedInstance);
    if (!nextInstance || !tryHydrate(fiber, nextInstance)) {
      // 对比了两个dom发现都无法和fiber混合,因此调用insertNonHydratedInstance控制台提示错误
      insertNonHydratedInstance(hydrationParentFiber, fiber);
      isHydrating = false;
      hydrationParentFiber = fiber;
      return;
    }
    // 如果第一次tryHydrate不满足,第二次tryHydrate满足,则说明兄弟节点和当前fiber是可以混合的,此时需要删除当前位置的dom
    deleteHydratableInstance(hydrationParentFiber, firstAttemptedInstance);
  }
  hydrationParentFiber = fiber;
  nextHydratableInstance = getFirstHydratableChild(nextInstance);
}
// 将dom实例保存在 fiber.stateNode上
function tryHydrate(fiber, nextInstance) {
  switch (fiber.tag) {
    case HostComponent: {
      if (
        nextInstance.nodeType === ELEMENT_NODE &&
        fiber.type.toLowerCase() === nextInstance.nodeName.toLowerCase()
      ) {
        fiber.stateNode = nextInstance;
        return true;
      }
      return false;
    }
    case HostText: {
      var text = fiber.pendingProps;
      if (text !== "" && nextInstance.nodeType === TEXT_NODE) {
        fiber.stateNode = nextInstance;
        return true;
      }
      return false;
    }
    default:
      return false;
  }
}

completeUnitOfWork

completeUnitOfWork 阶段主要是给 dom 关联 fiber 以及 props:dom.__reactProps$ = fiber.pendingProps;dom.__reactFiber$ = fiber;同时对比fiber.pendingPropsdom.attributes的差异

function completeUnitOfWork(unitOfWork) {
  var completedWork = unitOfWork;
  do {
    var current = completedWork.alternate;
    var returnFiber = completedWork.return;
    next = completeWork(current, completedWork, subtreeRenderLanes);
    var siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      workInProgress = siblingFiber;
      return;
    }
    completedWork = returnFiber;
    workInProgress = completedWork;
  } while (completedWork !== null);
}
function completeWork(current, workInProgress, renderLanes) {
  switch (workInProgress.tag) {
    case HostRoot: {
      if (current === null) {
        var wasHydrated = popHydrationState(workInProgress);
        if (wasHydrated) {
          markUpdate(workInProgress);
        }
      }
      return null;
    }
    case HostComponent:
      // 第一次渲染
      if (current === null) {
        var _wasHydrated = popHydrationState(workInProgress);
        if (_wasHydrated) {
          // 如果存在差异的属性,则将fiber副作用标记为更新
          if (prepareToHydrateHostInstance(workInProgress)) {
            markUpdate(workInProgress);
          }
        } else {
        }
      }
    case HostText: {
      var newText = newProps;
      if (current === null) {
        var _wasHydrated2 = popHydrationState(workInProgress);
        if (_wasHydrated2) {
          if (prepareToHydrateHostTextInstance(workInProgress)) {
            markUpdate(workInProgress);
          }
        }
      }
      return null;
    }
  }
}

popHydrationState

function popHydrationState(fiber) {
  if (fiber !== hydrationParentFiber) {
    return false;
  }
  if (!isHydrating) {
    popToNextHostParent(fiber);
    isHydrating = true;
    return false;
  }
  var type = fiber.type;
  if (
    fiber.tag !== HostComponent ||
    !shouldSetTextContent(type, fiber.memoizedProps)
  ) {
    var nextInstance = nextHydratableInstance;
    while (nextInstance) {
      deleteHydratableInstance(fiber, nextInstance);
      nextInstance = getNextHydratableSibling(nextInstance);
    }
  }
  popToNextHostParent(fiber);
  nextHydratableInstance = hydrationParentFiber
    ? getNextHydratableSibling(fiber.stateNode)
    : null;
  return true;
}

以下图为例:

在 beginWork 阶段对 p#B fiber 工作时,发现 dom 树中同一位置的h1#B不满足混合的条件,于是继续对比h1#B的兄弟节点,即div#C,仍然无法混合,经过最多两轮对比后发现p#B这个 fiber 没有可以混合的 dom 节点,于是将 isHydrating 标记为 false,hydrationParentFiber = fiberP#Bp#B的子孙节点都不再进行混合的过程。

div#B1fiber 没有子节点,因此它可以调用completeUnitOfWork完成工作,completeUnitOfWork 阶段调用 popHydrationState 方法,在popHydrationState方法内部,首先判断 fiber !== hydrationParentFiber,由于此时的hydrationParentFiber等于p#B,因此条件成立,不用往下执行。

由于p#B fiber 的子节点都已经完成了工作,因此它也可以调用completeUnitOfWork完成工作。同样的,在popHydrationState函数内部,第一个判断fiber !== hydrationParentFiber不成立,两者是相等的。第二个条件!isHydrating成立,进入条件语句,首先调用popToNextHostParenthydrationParentFiber设置为p#B的第一个类型为HostComponent的祖先元素,这里是div#A fiber,然后将isHydrating设置为 true,指示可以为p#B的兄弟节点进行混合。

如果服务端返回的 DOM 有多余的情况,则调用deleteHydratableInstance将其删除,比如下图中div#D节点将会在div#Afiber 的completeUnitOfWork阶段删除

prepareToHydrateHostInstance

对于HostComponent类型的fiber会调用这个方法,这里只要是关联 dom 和 fiber:

  • 设置domInstance.__reactFiber$w63z5ormsqk = fiber
  • 设置domInstance.__reactProps$w63z5ormsqk = props
  • 对比服务端和客户端的属性
function prepareToHydrateHostInstance(fiber) {
  var domInstance = fiber.stateNode;
  var updatePayload = hydrateInstance(
    domInstance,
    fiber.type,
    fiber.memoizedProps,
    fiber
  );
  fiber.updateQueue = updatePayload;
  if (updatePayload !== null) {
    return true;
  }
  return false;
}
function hydrateInstance(domInstance, type, props, fiber) {
  precacheFiberNode(fiber, domInstance); // domInstance.__reactFiber$w63z5ormsqk = fiber
  updateFiberProps(domInstance, props); // domInstance.__reactProps$w63z5ormsqk = props
  // 比较dom.attributes和props的差异,如果dom.attributes的属性比props多,说明服务端添加了额外的属性,此时控制台提示。
  // 注意,在对比过程中,只有服务端和客户端的children属性(即文本内容)不同时,控制台才会提示错误,同时在commit阶段,客户端会纠正这个错误,以客户端的文本为主。
  // 但是,如果是id不同,则客户端并不会纠正。
  return diffHydratedProperties(domInstance, type, props);
}

这里重点讲下diffHydratedProperties,以下面的demo为例:

// 服务端对应的dom
<div id="root"><div extra="server attr" id="server">客户端的文本</div></div>
// 客户端
render() {
  const { count } = this.state;
  return <div id="client">客户端的文本</div>;
}

diffHydratedProperties的过程中发现,服务端返回的id和客户端的id不同,控制台提示id不匹配,但是客户端并不会纠正这个,可以看到浏览器的id依然是server

同时,服务端多返回了一个extra属性,因此需要控制台提示,但由于已经提示了id不同的错误,这个错误就不会提示。

最后,客户端的文本和服务端的children不同,即文本内容不同,也需要提示错误,同时,客户端会纠正这个文本,以客户端的为主。

prepareToHydrateHostTextInstance

对于HostText类型的fiber会调用这个方法,这个方法逻辑比较简单,就不详细介绍了 务端对应的dom

<div id="root"><div extra="server attr" id="server">客户端的文本</div></div>
// 客户端
render() {
  const { count } = this.state;
  return <div id="client">客户端的文本</div>;
}

diffHydratedProperties的过程中发现,服务端返回的id和客户端的id不同,控制台提示id不匹配,但是客户端并不会纠正这个,可以看到浏览器的id依然是server

同时,服务端多返回了一个extra属性,因此需要控制台提示,但由于已经提示了id不同的错误,这个错误就不会提示。

最后,客户端的文本和服务端的children不同,即文本内容不同,也需要提示错误,同时,客户端会纠正这个文本,以客户端的为主。

prepareToHydrateHostTextInstance

对于HostText类型的fiber会调用这个方法,这个方法逻辑比较简单,就不详细介绍了

以上就是React Hydrate原理源码解析的详细内容,更多关于React Hydrate原理的资料请关注我们其它相关文章!

(0)

相关推荐

  • 使用reactjs优化了进度条页面性能提高70%

    目录 前言 进度条的应用场景 不推荐的写法 推荐的写法 缺陷 缺陷:这两种方案都会引发频繁的重排和重绘 重排: 重绘: 极致的优化 小彩蛋 「优化前」 「优化后」 结尾 前言 大家好,我是零一.最近我准备在组里进行代码串讲,所以我梳理了下项目之前的业务代码.在梳理的过程中,我看到了有个进度条组件写的非常好,这又想起我刚开始学前端时写的进度条的代码,跟这个比起来真的差距太大了(大部分的初学者应该都想不到,而且我第一次家实习公司带我的mentor亦是如此). 因此,我想给大家分享一下这个思路极好的进

  • react源码合成事件深入解析

    目录 引言 导火线 事件委托 合成事件特点 React 事件系统 事件注册 enqueuePutListener() listenTo() trapCapturedEvent 与 trapBubbledEvent 事件存储 事件分发 事件执行 构造合成事件 批处理 引言 温馨提示: 下边是对React合成事件的源码阅读,全文有点长,但是!如果你真的想知道这不为人知的背后内幕,那一定要耐心看下去! 最近在做一个功能,然后不小心踩到了 React 合成事件 的坑,好奇心的驱使,去看了 React 官

  • ReactJS中不同类型的状态详解

    目录 引言 误解 服务器端状态与客户端状态 什么是React Query? 总结 引言 在这篇文章中,我们将介绍ReactJS中不同类型的状态,以及我们是如何轻易地将这些类型混为一谈的.我们还将讨论哪种状态应该使用哪种工具/库/包.这篇文章将充满了多汁的技术,所以要保持专注.所以不要浪费任何时间,让我们开始吧 误解 在已经配置好redux的情况下,利用redux的一个误区是当开发者开始为他们应用中的所有状态使用redux时,这样做是为了避免道具的钻取,并通过用商店包装你的应用,在你的应用的各个层

  • ReactJS 应用兼容ios9对标ie11解决方案

    目录 背景 遇到问题 解决方案 初始配置 安装@babel/preset-env 安装 @babel/plugin-proposal-decorators 和 @babel/plugin-proposal-class-properties 安装promise 结语 背景 最近遇到了一个比较棘手的问题,客户要求我们的react应用在ios9上运行,我们的应用在ios9上是白屏显示,所以需要做一些兼容. 遇到问题 遇到问题之后有一个更大的问题就是手上没有ios9的机器,毕竟这个太他娘的古老了,我就去

  • React Hydrate原理源码解析

    目录 引言 Demo ReactDOM.render ReactDOM.hydrate hydrate 过程 事件绑定 hydrate 源码剖析 beginWork HostRoot Fiber HostComponent HostText Fiber tryToClaimNextHydratableInstance completeUnitOfWork popHydrationState prepareToHydrateHostInstance prepareToHydrateHostText

  • Spring的Model 和 Map的原理源码解析

    Model 和 Map 为什么在Model和Map中放值传入后会出现在request的上面. 9.1.源码解析 准备测试代码 @GetMapping("/goto") public String go(HttpServletRequest request, Map<String,Object> map, Model model){ request.setAttribute("msg","传过来...."); map.put("

  • MyBatis框架底层的执行原理源码解析

    目录 1.前言 2.案例项目源码 3.MyBatis源码解析底层执行原理 3.1 读取mybatis配置文件创建出SqlSeesionFactory对象 3.2 通过SqlSeesionFactory对象进而创建出SqlSession对象 3.3 通过SqlSession的getMapper获取到接口代理对象 3.4 通过mapper接口的代理对象执行CRUD 1.前言 MyBatis框架大家肯定都用过的,废话我就不再多说了,这篇文章就给大家分享一下有关MyBatis框架底层的执行原理吧(Deb

  • go slice 扩容实现原理源码解析

    目录 正文 扩容的示例 实际扩容倍数 growslice 实现 growslice 实现步骤 growslice 源码剖析 总结 正文 基于 Go 1.19. go 的切片我们都知道可以自动地进行扩容,具体来说就是在切片的容量容纳不下新的元素的时候, 底层会帮我们为切片的底层数组分配更大的内存空间,然后把旧的切片的底层数组指针指向新的内存中: 目前网上一些关于扩容倍数的文章都是基于相对旧版本的 Go 的,新版本中,现在切片扩容的时候并不是那种准确的小于多少容量的时候就 2 倍扩容, 大于多少容量

  • axios拦截器工作方式及原理源码解析

    目录 axios 拦截器的配置方式 use() 方法的定义 拦截器如何执行 拦截器回调方法的添加顺序 同步执行请求拦截器(顺序执行) 异步执行请求拦截器(同时执行) Q&A 拦截器是如何工作的 拦截器的执行顺序 同步&异步 axios 拦截器的配置方式 本文所用 axios 版本号为:1.3.2. axios 中有两种拦截器: axios.interceptors.request.use(onFulfilled, onRejected, options):配置请求拦截器. onFulfil

  • async-validator实现原理源码解析

    目录 async-validator 介绍 async-validator 基本使用 async-validator 源码分析 async-validator 源码-构造函数 async-validator 源码-validate方法 async-validator 源码-register方法 总结 最后 async-validator 介绍 async-validator是异步的验证数据是否合法有效的工具, 内置了不同数据类型的常见验证规则. 在需要对数据进行验证的场景中,都可以考虑使用asy

  • python装饰器原理源码示例分析

    目录 前言 一.什么是装饰器 二.为什么要用装饰器 三.简单的装饰器 四.装饰器的语法糖 五.装饰器传参 六.带参数的装饰器 七.类装饰器 八.带参数的类装饰器 九.装饰器的顺序 前言 最近有人问我装饰器是什么,我就跟他说,其实就是装饰器就是类似于女孩子的发卡.你喜欢的一个女孩子,她可以有很多个发卡,而当她戴上不同的发卡,她的头顶上就是装饰了不同的发卡.但是你喜欢的女孩子还是你喜欢的女孩子.如果还觉得不理解的话,装饰器就是咱们的手机壳,你尽管套上了手机壳,但并不影响你的手机功能,可你的手机还是该

  • spring-session简介及实现原理源码分析

    一:spring-session介绍 1.简介 session一直都是我们做集群时需要解决的一个难题,过去我们可以从serlvet容器上解决,比如开源servlet容器-tomcat提供的tomcat-redis-session-manager.memcached-session-manager. 或者通过nginx之类的负载均衡做ip_hash,路由到特定的服务器上.. 但是这两种办法都存在弊端. spring-session是spring旗下的一个项目,把servlet容器实现的httpSe

  • react diff算法源码解析

    React中Diff算法又称为调和算法,对应函数名为reconcileChildren,它的主要作用是标记更新过程中那些元素发生了变化,这些变化包括新增.移动.删除.过程发生在beginWork阶段,只有非初次渲染才会Diff. 以前看过一些文章将Diff算法表述为两颗Fiber树的比较,这是不正确的,实际的Diff过程是一组现有的Fiber节点和新的由JSX生成的ReactElement的比较,然后生成新的Fiber节点的过程,这个过程中也会尝试复用现有Fiber节点. 节点Diff又分为两种

  • React事件机制源码解析

    React v17里事件机制有了比较大的改动,想来和v16差别还是比较大的. 本文浅析的React版本为17.0.1,使用ReactDOM.render创建应用,不含优先级相关. 原理简述 React中事件分为委托事件(DelegatedEvent)和不需要委托事件(NonDelegatedEvent),委托事件在fiberRoot创建的时候,就会在root节点的DOM元素上绑定几乎所有事件的处理函数,而不需要委托事件只会将处理函数绑定在DOM元素本身. 同时,React将事件分为3种类型--d

随机推荐