4

First of all, by "re-render", here it means either

  1. the render() method of any class component is called, OR
  2. the function of the function component is called.

Let's called the element in the actual DOM changing a "refresh", to distinguish it from "re-render".

Is the rule of "re-render" as simple as:

When any state of a component is changed, then the component and all the subtree down from this component is re-rendered

and that's it? For example:

function A() {
  console.log("Component A re-render");

  return <div>Component A says Hello World</div>;
}

function App() {
  const [counter, setCounter] = React.useState(0);

  console.log("Component App re-render");

  function increaseCount() {
    setCounter(c => c + 1);
  }

  return (
    <div>
      {counter}
      <button onClick={increaseCount} >Increment</button>
      <A />
    </div>
  )
}

ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react@16.12.0/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js" crossorigin></script>


<div id="root"></div>

Component A is so simple: it doesn't even take any props and is just outputting static text, yet it is still called every time (by looking at the console.log() output).

But even though it is "re-rendered", the actual DOM element is not "refreshed", as seen in Google Chrome's Inspect Element that the DOM element is not flashing for Component A, but is only flashing for the counter number.

So is this how it works?

  1. Whenever any state of a component is changed, that component and the whole subtree will be "re-rendered".
  2. But ReactJS will "reconcile" the content of the "Virtual DOM" that it built using those JSX with the content of the actual DOM, and if the content is different, "refresh the actual DOM".

But having said that, it seems ReactJS doesn't actually reconcile with the actual DOM, but reconcile with probably the "previous virtual DOM". Why? Because if I use a setTimeout() to change the actual DOM of Component A to some other content after 3 seconds, and click the button, ReactJS doesn't change the content of Component A back to "Hello World". Example:

function A() {
  console.log("Component A re-render");

  return <div id="foo">Component A says Hello World</div>;
}

function App() {
  const [counter, setCounter] = React.useState(0);

  console.log("Component App re-render");

  function increaseCount() {
    setCounter(c => c + 1);
  }

  return (
    <div>
      {counter}
      <button onClick={increaseCount} >Increment</button>
      <A />
    </div>
  )
}

ReactDOM.render(<App />, document.querySelector("#root"));

setTimeout(function() {
  document.querySelector("#foo").innerText = "hi"
}, 3000);
<script src="https://unpkg.com/react@16.12.0/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js" crossorigin></script>


<div id="root"></div>
nonopolarity
  • 146,324
  • 131
  • 460
  • 740
  • I really wonder what it is about getting downvoted like it is not a valid programming question – nonopolarity Feb 18 '20 at 08:03
  • React reconciliation is not the diffing between Virtual DOM and real DOM, it's about the diffing between previous Virtual DOM tree and the current one, then update the actual DOM based on the result. In your experiment, the algorithm decided that there's no need to update the DOM from Component A (I don't know exactly how they decided that, you can look up the [reconciliation doc](https://reactjs.org/docs/reconciliation.html) for more details) – Son Dang Feb 18 '20 at 09:19
  • Also an interesting experiment based on yours, is even though some code provide the exact DOM output, but different coding styles give a different reconciliation result. See [CodeSandbox](https://codesandbox.io/s/festive-blackwell-znrtq) – Son Dang Feb 18 '20 at 09:22
  • @SonDang so your Component A and B only differ by how the text is generated, and one got updated, while the other did not? That's so strange... if I remove your useEffect(), then both will update... – nonopolarity Feb 18 '20 at 10:13
  • i guess if children is array of text element react creates text nodes and appends them to div. And when the second text node is updated react updates the second node. But since it's not in the dom tree nothing is changed. – a-c-sreedhar-reddy Feb 18 '20 at 11:21
  • yup... I found that too... I can't change the text node by `document.getElementById("B").firstChild = "abc"` and if I create two children under `#B` it still won't update, so looks like React remember the second text node and updates it, and it is no longer on the screen – nonopolarity Feb 18 '20 at 11:28
  • but we can use `document.getElementById("B").firstChild.nodeValue = "Hi B"; document.getElementById("B").firstChild.nextSibling.nodeValue = "Hi B";` if we want, and React can update the second node... so looks like React also uses `node.nodeValue` to alter the content for the text node – nonopolarity Feb 18 '20 at 11:36

3 Answers3

1

After rendering react gets a json of how the view should look like, calculates the changes and then changes the actual dom. So even though A is rerendered the output json will be same as virtual dom's and hence react does not touch the dom

1

This is to answer the WHY part of your question -

As explained in this link - https://gist.github.com/paulirish/5d52fb081b3570c81e3a, many things can trigger reflow of the DOM. Not just setting, even accessing the DOM element properties can cause reflow of DOM which is a very costly process.

So, if React starts comparing against the actual DOM, then it will bring down the performance of rendering rather than improving it. This is why react compares changes with previous copy and update the changes to the Actual DOM if required.

0

After reading the React docs on Reconciliation, it seems a simplified way to think about it is:

  1. Whenever the props provided to the component COMPO1 or the state of this component changes, then all the render() of class components and the function components under COMPO1 are called, to form a virtual DOM tree, and the whole subtree is compared to the previous subtree -- not to the actual DOM but to a previous virtual DOM subtree
  2. It is compared to see if the nodes are different and recursively do that for all children.
  3. Only the minimum subtrees from the component and down that are different would cause a refresh of content to the actual DOM -- meaning if node A has children B and C, and B stayed the same while node C became different, then only C will cause that part of the actual DOM to be refreshed. (of course if node C has node D and E and D stayed the same while E became different, then only E is refreshed to actual DOM).
nonopolarity
  • 146,324
  • 131
  • 460
  • 740