简体   繁体   English

在 state 更改后,React 如何更新组件及其子组件?

[英]How does React update a component and its children after a state change?

I am watching Paul O Shannessy - Building React From Scratch我正在看Paul O Shannessy - 从零开始构建 React

And I understand the mounting process very well but I have hard day trying to understand how React update a component and its children而且我非常了解安装过程,但我很难理解 React 如何更新组件及其子组件

The reconciler controls the update process by this method: reconciler 通过这种方法控制更新过程:

function receiveComponent(component, element) {
  let prevElement = component._currentElement;
  if (prevElement === element) {
    return;
  }

  component.receiveComponent(element);
}

Component.receiveComponent

 receiveComponent(nextElement) {
    this.updateComponent(this._currentElement, nextElement);
  }

and this is the Component.updateComponent method:这是Component.updateComponent方法:

  updateComponent(prevElement, nextElement) {
    if (prevElement !== nextElement) {
      // React would call componentWillReceiveProps here
    }

    // React would call componentWillUpdate here

    // Update instance data
    this._currentElement = nextElement;
    this.props = nextElement.props;
    this.state = this._pendingState;
    this._pendingState = null;

    let prevRenderedElement = this._renderedComponent._currentElement;
    let nextRenderedElement = this.render();
 
    if (shouldUpdateComponent(prevRenderedElement, nextRenderedElement)) {
      Reconciler.receiveComponent(this._renderedComponent, nextRenderedElement);
    } 
  }

This is the part of the code that updates the component after state change, and i assume that it should update the children too, but i can't understand how this code achieves that, in the mounting process React instantiate components to dive deeper in the tree but this doesn't happen here, we need to find the first HTML element then we can change our strategy and update that HTML element in another place in the code, and I can't find any way to find any HTML elements this way.这是在 state 更改后更新组件的代码部分,我认为它也应该更新孩子,但我不明白这段代码是如何实现的,在安装过程中反应实例化组件以深入了解树,但这不会在这里发生,我们需要找到第一个 HTML 元素,然后我们可以更改策略并在代码的另一个位置更新 HTML 元素,我找不到任何方法来找到任何 Z4C4AD5FCA2E7A3F74DBB1CED0038 元素.

Finding the first HTML is the way to stop this endless recursion and logically this is what I expect from the code, to stop recursion the same way in the mounting process, but in mounting, this demanded component instantiation so we can delegate to the reconciler that will discover that we are dealing with a wrapper instance of an HTML element not a wrapper instance of a custom component then React can place that HTML element in the DOM.找到第一个 HTML 是停止这种无休止递归的方法,从逻辑上讲,这就是我对代码的期望,在挂载过程中以相同的方式停止递归,但是在挂载时,这需要组件实例化,因此我们可以委托给协调器会发现我们正在处理 HTML 元素的包装器实例,而不是自定义组件的包装器实例,然后 React 可以将该 HTML 元素放在 DOM 中。

I can't understand how the code works in the update process.我不明白代码在更新过程中是如何工作的。 this code as I see won't dive deeper in the tree and I think won't update the children and can't let React find the first HTML element so React can update the DOM element, isn't it?我看到的这段代码不会深入树中,我认为不会更新子元素,也不能让 React 找到第一个 HTML 元素,所以 React 可以更新 DOM 元素,不是吗?

This is the code repo on Github这是Github上的代码仓库

I think React not re-render parent component first instead of that, React re-render child component first.我认为 React 不会先重新渲染父组件,而是先重新渲染子组件。

Example: A (parent) -> B (child) -> C (child of B) When A update state C (re-render) -> B -> A示例:A(父)-> B(子)-> C(B 的子)当 A 更新 state C(重新渲染)-> B

React completely copy the actual DOM and create the virtual DOM in javascript. React 完全复制实际 DOM 并在 javascript 中创建虚拟 DOM。 In our application whenever we update any of the data that ends up being rendered in our components, React does not rerender the entire DOM.在我们的应用程序中,每当我们更新任何最终在组件中呈现的数据时,React 都不会重新呈现整个 DOM。 It only affects the thing that matters.它只影响重要的事情。 So react actually copies the virtual DOM again.所以 react 实际上再次复制了虚拟 DOM。 This time it applies the changes to the data that got updated.这次它将更改应用于已更新的数据。

在此处输入图像描述

It will make the change in the red component and then it will compare this virtual DOM to the old DOM.它将在红色组件中进行更改,然后将这个虚拟 DOM 与旧 DOM 进行比较。 It will see the different part.它将看到不同的部分。 Then it will apply the DOM changes only to that different component.然后它将仅将 DOM 更改应用于该不同的组件。

The updating phase starts if props or the state changes.如果 props 或 state 发生更改,则更新阶段开始。 If the data at the top level changes:如果顶层数据发生变化:

在此处输入图像描述

If it is passing that data down to its children, all the children are going to be rerendered.如果它将数据传递给它的孩子,所有孩子都将被重新渲染。 If the state of the component at the mid-level gets changed:如果中级组件的state发生变化:

在此处输入图像描述

This time only its children will get rerendered.这次只有它的孩子会被重新渲染。 React will rerender any part of the tree below that node. React 将重新渲染该节点下方的树的任何部分。 Because the data that generates the children components' view actually sits at the parent component(mid-level one).因为生成子组件视图的数据实际上位于父组件(中级组件)。 But anything above it, the parent or the siblings will not rerender.但是任何高于它的东西,父母或兄弟姐妹都不会重新渲染。 because data does not affect them.因为数据不会影响他们。 this concept is called Unidirectional Data Flow .这个概念称为Unidirectional Data Flow

You can see in action in chrome browser.您可以在 chrome 浏览器中查看实际操作。 chose the rendering and then enable the painting flushing option选择渲染,然后启用painting flushing选项

在此处输入图像描述

If you make any change on the page, you will see that updated components will be flashed.如果您在页面上进行任何更改,您将看到更新的组件将闪烁。

Hey Consider using a Tree data structure for your need, ReactJs follows a unidirectional manner of Updating the state ie As soon as the there is a Change in the parent state then all the children which are passed on the props that are residing in the Parent Component are updated once and for all! Hey Consider using a Tree data structure for your need, ReactJs follows a unidirectional manner of Updating the state ie As soon as the there is a Change in the parent state then all the children which are passed on the props that are residing in the Parent Component一劳永逸地更新! Consider using something known as Depth First Search as an algo option which will find you the Node that connects to the parent and once you reach that node, you check for the state and if there is a deviation from the state variables that are shared by the parent you can update them!考虑使用称为深度优先搜索的算法选项,它将找到连接到父节点的节点,一旦到达该节点,检查 state 以及是否与 state 共享的变量存在偏差父母你可以更新他们!

Note: This may all seem a bit theoretical but if you could do something remotely close to this thing you will have created a way to update components just how react does!注意:这可能看起来有点理论,但如果你可以远程做一些接近这个东西的事情,你将创建一种更新组件的方法,就像 react 一样!

I found out experimentally that React will only re-render elements if it have to, which is always, except for {children} and React.memo() .我通过实验发现 React 只会在必要时重新渲染元素,除了{children}React.memo()之外,总是如此。

Using children correctly, together with batched dom updates makes a very efficient and smooth user experience.正确使用孩子,以及批量 dom 更新,可以提供非常高效和流畅的用户体验。

consider this case:考虑这种情况:

function App() {
  return <div>
    <Parent>
      <Child01/>
      <Child01/>
    </Parent>
    <Child03/>
  </div>
}

function Parent({children}) {
  const [state, setState] = useState(0);

  return <div>
    <button onClick={x => x+1)>click</button>
    <Child02 />
    {children}
  </div>
}

when clicking on the button, you will get the following:单击按钮时,您将获得以下信息:

- button click
- setState(...), add Parent to dirty list
- start re-rendering all dirty nodes
- Parent rerenders
- Child02 rerenders
- DONE

Note that注意

  • Parent ( app ) and sibling ( Child03 ) nodes will not get re-rendered, or you'll end up with a re-render recursion.父节点 ( app ) 和兄弟节点 ( Child03 ) 不会被重新渲染,否则您最终会得到重新渲染递归。
  • Parent is re-rendered because its state has changed, so its output has to be recalculated. Parent被重新渲染是因为它的state已经改变,所以它的output必须重新计算。
  • {children} have not been affected by this change, so it stays the same. {children}没有受到此更改的影响,因此保持不变。 (unless a context is involved, but that's a different mechanism). (除非涉及上下文,但这是一种不同的机制)。
  • finally, <Child02 /> has been marked dirty, because that part of the virtual dom has been touched.最后, <Child02 />已被标记为脏,因为虚拟 dom 的那部分已被触及。 While it's trivial for us to see it was not effected, the only way React could verify it is by comparing props, which is not done by default!虽然我们很容易看到它没有受到影响,但 React 可以验证它的唯一方法是比较 props,默认情况下不会这样做!
  • the only way to prevent Child02 from rendering is wrapping it with React.memo , which might be slower than just re-rendring it.防止 Child02 渲染的唯一方法是用React.memo包装它,这可能比重新渲染要慢。

I created a codesandbox to dig in我创建了一个代码沙盒来挖掘

Here is the codesandbox I created这是我创建的代码框

and here's a short recording of me opening the debugger and seeing the call stack.这是我打开调试器并查看调用堆栈的简短记录

How it works这个怎么运作

Starting from where you left off, Component.updateComponent:从你离开的地方开始,Component.updateComponent:

  updateComponent(prevElement, nextElement) {
  //...
    if (shouldUpdateComponent(prevRenderedElement, nextRenderedElement)) {
      Reconciler.receiveComponent(this._renderedComponent, nextRenderedElement);
  //...

in the Component.updateComponent method Reconciler.receiveComponent is called which calls component.receiveComponent(element);Component.updateComponent方法中调用Reconciler.receiveComponent调用component.receiveComponent(element);

Now, this component refers to this._renderedComponent and is not an instance of Component but of DOMComponentWrapper现在,这个component引用this._renderedComponent并且不是Component的实例,而是DOMComponentWrapper

and here's the receiveComponent method of DOMComponentWrapper :这是 DOMComponentWrapper 的DOMComponentWrapper方法:

  receiveComponent(nextElement) {
    this.updateComponent(this._currentElement, nextElement);
  }

  updateComponent(prevElement, nextElement) {
    // debugger;
    this._currentElement = nextElement;
    this._updateDOMProperties(prevElement.props, nextElement.props);
    this._updateDOMChildren(prevElement.props, nextElement.props);
  }

Then _updateDOMChildren ends up calling the children render method.然后_updateDOMChildren最终调用子render方法。

here's a call stack from the codesandbox I created to dig in.这是我创建的用于挖掘的代码盒中的调用堆栈。

从 setState 调用堆栈直到子渲染

How do we end up in DOMComponentWrapper我们如何在 DOMComponentWrapper 中结束

in the Component 's mountComponent method we have:ComponentmountComponent方法中,我们有:

let renderedComponent = instantiateComponent(renderedElement);
this._renderedComponent = renderedComponent;

and in instantiateComponent we have:instantiateComponent中,我们有:

  let type = element.type;

  let wrapperInstance;
  if (typeof type === 'string') {
    wrapperInstance = HostComponent.construct(element);
  } else if (typeof type === 'function') {
    wrapperInstance = new element.type(element.props);
    wrapperInstance._construct(element);
  } else if (typeof element === 'string' || typeof element === 'number') {
    wrapperInstance = HostComponent.constructTextComponent(element);
  }

  return wrapperInstance;

HostComponent is being injected with DOMComponentWrapper in dilithium.js main file: HostComponent 在dilithium.js主文件中被DOMComponentWrapper注入:

HostComponent.inject(DOMComponentWrapper);

HostComponent is only a kind of proxy meant to invert control and allow different Hosts in React. HostComponent 只是一种代理,旨在反转控制并允许 React 中的不同主机。

here's the inject method:这是inject方法:

function inject(impl) {
  implementation = impl;
}

and the construct method:construct方法:

function construct(element) {
  assert(implementation);

  return new implementation(element);
}

Another answer might be the structure of the Fiber tree.另一个答案可能是 Fiber 树的结构。 During execution, react renders a ReactComponent into an object made out of ReactNode s and props.在执行期间,react 将ReactComponent渲染为由ReactNode和 props 组成的 object。 These ReactNode s are assembled into a FiberNode tree (which might be the in memory representation of the virutal dom?).这些ReactNode被组装成一个FiberNode树(可能是 memory 中虚拟 dom 的表示?)。

In the FiberNode tree, depending on the traversal algorithm (children first, sibling first, etc), React always has a single "next" node to continue.FiberNode树中,根据遍历算法(孩子优先,兄弟优先等),React 总是有一个“下一个”节点继续。 So, React will dive deeper into the tree, and update FiberNode s, as it goes along.因此,React 将深入到树中,并随着它的进行更新FiberNode

If we take the same example,如果我们举同样的例子,

function App() {
  return <div>
    <Parent>
      <Child01/>
      <Child01/>
    </Parent>
    <Child03/>
  </div>
}

function Parent({children}) {
  const [state, setState] = useState(0);

  return <div>
    <button onClick={x => x+1)>click</button>
    <Child02 />
    {children}
  </div>
}

Which React will transform into this FiberNode tree:哪个 React 将转换成这个 FiberNode 树:

node01 = { type: App, return: null, child: node02, sibling: null }
node02 = { type: 'div', return: node01, child: node03, sibling: null }
node03 = { type: Parent, return: node02, child: node05(?), sibling: node04 }
node04 = { type: Child03, return: node02, child: null, sibling: null }
node05 = { type: Child01, return: node03, child: null, sibling: node06 }
node06 = { type: Child01, return: node03, child: null, sibling: null }

// Parent will spawn its own FiberTree,
node10 = { type: 'div', return: node02, child: node11, sibling: null }
node11 = { type: 'button', return: node10, child: null, sibling: node12 }
node12 = { type: Child02, return: node10, child: null, sibling: node05 }

I might have missed something (ie. node03's child might be node10), but the idea is this - React always have a single node (the 'next' node) to render when it traverses the fiber tree.我可能错过了一些东西(即 node03 的子节点可能是 node10),但想法是这样的——React 在遍历纤维树时总是有一个节点(“下一个”节点)要渲染。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM