react dom virtual experience

  React mixed HTML and JavaScript syntax (called JSX) in the preparation of the component. However, the browser on the JSX and knew nothing about grammar, the browser can only understand pure JavaScript, so JSX must be converted to HTML. This is a div of JSX code, which has a class and some of the content:

< Div className = 'CN' > 
  text 
</ div >

After this jsx React will become common js it is a function call with a number of parameters:

React.createElement (
   'div' , 
  {className: 'CN' },
   'text' 
);

Its first argument is a string that corresponds to the html tag name, and the second parameter is the object constituted all of its properties, of course, it is also possible that an empty object, and the remaining parameters are elements of this child elements, text here will be treated as a child element, so the third parameter is "text."

Here you should be able to imagine this element more `children` of what happens.

<div className = 'cn'> 
  text. 1
   <br />
   Text 2
 </ div>

 

 

React.createElement(
  'div',
  { className: 'cn' },
  '文本1',              // 1st child
  React.createElement('br'), // 2nd child
  '文本1'               // 3rd child
)

 

The current function has five parameters: the type of element, all attributes of an object and three child elements. Because a child is known React HTML tags, it will be interpreted as a function call.

So far, we have introduced two types of child parameters, one is a plain text string, one is React.createElement call other functions. In fact, other values ​​can be used as parameters, such as:

  • Basic types of false, null, undefined and true
  • Array
  • React Components

Using arrays because subassembly may be grouped and passed as a parameter:

React.createElement(
  'div',
  { className: 'cn' },
  ['Content 1!', React.createElement('br'), 'Content 2!']
)

Of course, React the power does not come from the label `HTML` described in the Code, but from components created by the user, for example:

function Table({ rows }) {
  return (
    <table>
      {rows.map(row => (
        <tr key={Domain Premium: row.id}>
          <td>{row.title}</td>
        </tr>
      ))}
    </table>
  );
}

Component allows developers to decompose into reusable template block. In the example of the above components "pure function", the assembly comprising an array of objects to accept the data table row, and returns to `React.createElement` <table> elements as a single call and the line of the sub-elements.

Whenever a developer when the assembly into the JSX layout it looks like this:

<Table rows={rows} />

But from the perspective of the browser, we see it is this:

React.createElement(Table, { rows: rows });

Please note, HTML elements of the first argument to `string` not described, but reference to the component (ie, function name). The second parameter is passed in the assembly `props` object.

The component on the page

Now the browser has to convert all the components JSX is pure JavaScript, the browser now get a bunch of function calls, the parameters of other function calls, there are other function calls ...... how to convert them to constitute a web page DOM element?

For this reason, developers need to use the library and its ReactDOM render method:

function Table({ rows }) { /* ... */ } // 组件定义
// 渲染一个组件
ReactDOM.render(
  React.createElement(Table, { rows: rows }), // "创建" 一个 component
  document.getElementById('#root') // 将它放入DOM中
);

When ReactDOM.render is called, React.createElement will eventually be called, it returns the following objects:

// 这个对象里还有很多其他的字段,但现在对开发者来说重要的是这些。
{
  type: Table,
  props: {
    rows: rows
  },
  // ...
}

These objects constitute the Virtual DOM in the sense React

They will be compared with each other in rendering all further and eventually converted into real DOM (contrast with Virtual DOM).

This is another example: this time there is a div with a class attribute and several sub-node:

React.createElement(
  'div',
  { className: 'cn' },
  'Content 1!',
  'Content 2!',
);

become:

{
  type: 'div',
  props: {
    className: 'cn',
    children: [
      'Content 1!',
      'Content 2!'
    ]
  }
}

All incoming expansion functions, that is, except for the first React.createElement second argument attributes the rest of the parameters are children in the props object, whether it is passed in a function of what they will eventually be passed as children props in.

Moreover, developers can directly add JSX code `children` property, directly on the child` children`, the result is still the same:

<div className='cn' children={['Content 1!', 'Content 2!']} />

After Virtual DOM object is created out ReactDOM.render attempts by the following rules to translate it into the browser DOM node can be able to understand:

  • If the type attribute Virtual DOM object is a tag name of the type string, you create a tag, containing all the props in the property.
  • If the type attribute Virtual DOM object is a function or class, you call it, it may still return a Virtual DOM then the results continue recursive call this procedure.
  • If the children property in props, on the right children of each element above process, and returns the result into the DOM parent node.

Finally, the following is obtained browser HTML (table of the above-described example):

<table>
  <tr>
    <td>Title</td>
  </tr>
  ...
</table>

DOM reconstruction

Continued browser to "rebuild" a DOM node, if you want to update a browser page, obviously, developers do not want to replace all the elements of a page, which is React real magic. How can we achieve it? Start with the easiest way to start, recall `ReactDOM.render` method of this node.

// 第二次调用
ReactDOM.render( React.createElement(Table, { rows: rows }), document.getElementById('#root') ); 

This time, the above code will be different from the execution logic to see the code. React than starting from scratch to create all DOM nodes and place them on the page, React will use the "diff" algorithm to determine which parts of the node tree must be updated and which parts can remain unchanged.

So how does it work? Only a few simple cases, they will understand React optimizer helps a lot. Remember, the next target is seen as a representation of the object React Virtual nodes in the DOM.

▌Case 1: type is a string, type remains unchanged between calls, props has not changed.

// before update
{ type: 'div', props: { className: 'cn' } }

// after update
{ type: 'div', props: { className: 'cn' } }

This is the simplest case: DOM remains unchanged.

▌Case 2: type remains the same string, props are different.

// before update:
{ type: 'div', props: { className: 'cn' } }

// after update:
{ type: 'div', props: { className: 'cnn' } }

Since the type still represents an HTML element, React know how the standard DOM API calls to change its properties without having to remove a node from the DOM tree.

▌Case 3: type has been changed to a different component or changed from String String component to component.

// before update:
{ type: 'div', props: { className: 'cn' } }

// after update:
{ type: 'span', props: { className: 'cn' } }

Since React now see a different type, it will not even attempt to update the DOM node: the old elements together with all its child nodes are deleted (unmount). Therefore, replacing disparate elements in the DOM tree the cost will be very high. Fortunately, this rarely happens in reality.

It is important to remember React use === (third class) to compare the value type, so they must be the same instances of the same class or the same function.

The next scene even more interesting, because it is the most commonly used developer React way.

▌Case 4: type is a component.

// before update:
{ type: Table, props: { rows: rows } }

// after update:
{ type: Table, props: { rows: rows } }

You might say, "It does not seem any change", but this is wrong.

If the type is a function or reference to the class (ie, conventional React component), and started the tree diff comparison process, we will always try to React to see all `child` internal components to ensure the return value of` render` not change. That compares each component under a tree - yes, sophisticated rendering may become expensive!

Components of children

In addition to the four common scenarios described above, when elements of multiple sub-elements, developers also need to consider the behavior of React. Assuming that there is such an element:

// ...
props: {
  children: [
      { type: 'div' },
      { type: 'span' },
      { type: 'br' }
  ]
},
// ...

Developers Developers want to re-render it in such a way ( `span` and` div` exchange position):

// ...
props: {
  children: [
    { type: 'span' },
    { type: 'div' },
    { type: 'br' }
  ]
},
// ...

So what will happen?

When React see any type of array inside props.children, it will proceed to the element in its previously seen with the elements of the array are compared sequentially: index 0 will, index 1 and index 1 compared with the index 0 for each pair of elements, the application of the above React compares the updated rule set. In the above example, it sees div become a span 3 This is the case in a scene. But there is a question: Suppose you want to remove the developer from the first line 1000 rows in the table. React must "update" the remaining 999 children, because if the previous one by one index representation compared to their content will now be unequal.

Fortunately, React there is a built-in method to solve this problem. If the key element has attributes, the key element will not by comparing the index. As long as the key is unique, it will move React element without removing them from the DOM tree, then put them back (referred to as the mounting process React / unloading).

// ...
props: {
  children: [ // 现在react就是根据key,而不是索引来比较了
    { type: 'div', key: 'div' },
    { type: 'span', key: 'span' },
    { type: 'br', key: 'bt' }
  ]
},
// ...

When the state changes

So far, this article only scratches the `props`, part React philosophy, but ignores the` state`. This is a simple "stateful" component:

class App extends Component {
  state = { counter: 0 }

  increment = () => this.setState({
    counter: this.state.counter + 1,
  })

  render = () => (<button onClick={this.increment}>
    {'Counter: ' + this.state.counter}
  </button>)
}

Now, `state` objects above has a counter property. Click the button to increase the value and change the button text. However, when the user clicks, DOM what happens? It's part of what will be recalculated and updated?

Call this.setState can lead to re-rendered, but does not cause weight rendering the entire page, which can only lead to the component itself and its children. Parents and siblings can be spared.

Fix the problem

This article prepared a DEMO , this is the way to fix the problem before. You can here view its source code. But before that, you also need to install React Developer Tools .

Open the demo first thing to look at is what led to Virtual DOM elements and when to update. Navigate to your browser Dev Tools in React panel, click Settings and then select "Highlight Updates" check box:

Now try to add a row in the table. As you can see, each element on the page will appear around the border. This means that every time you add rows, React will be calculated and compared the entire Virtual DOM tree. Now try pressing the counter button in the row. You will see how Virtual DOM update (state only relevant element and its children update).

React DevTools suggests that local issues that may arise, but did not tell any details Developer: especially problematic update means that there are different components are still unmount / mount after the element "diff". For more information, developers need to use React built-in analyzer (Please note that it does not work in production mode).

Go to the Chrome DevTools in the "Performance" tab. Click the record button and then click on the form. Adding lines, change some of the counters, and then click on the "Stop" button. Wait a minute after developers will see:

In the resulting output, developers need to focus on "Timing". Zoom timeline until you see the "React Tree Reconciliation" group and its children. These names are components of, next to [update] or [mount]. TableRow can see that there is a mount, and all the other TableRow in update, this is not the developers want.

Most performance problems by [update] or [mount] cause

A component (as well as everything in the assembly) for some reason remount each update, the developers do not want to let it happen (remount very slow), or an excessively high cost of redrawing the implementation on a large branch, even if the component seems nothing has changed.

Repair mount / unmount

Now, when developers know how to React decided to update the Virtual DOM and know what happened behind the scenes, finally ready to solve the problem! Fix performance problems we must first solve mount / unmount.

If the developer to combine multiple child element of any elements / components internally as an array, the program can be obtained very significant speed boost.

think about it:

<div>
  <Message /> <Table /> <Footer /> </div>

In the virtual DOM, it will be represented as:

// ...
props: {
  children: [
    { type: Message },
    { type: Table },
    { type: Footer }
  ]
}
// ...

A simple Message component (a div with some text, such as the top of the pig toothfish notice) and a long Table, say, more than 1,000 lines. They are div child elements, so they are placed under props.children parent node, and they have no key. React to remind developers will not even assign key from the console warning, because as a child node React.createElement parameter list instead of an array is passed to the parent.

Now, the user has closed the top of the notification, so `Message` removed from the tree. `Table`,` Footer` is the rest of the child.

// ...
props: {
  children: [
    { type: Table },
    { type: Footer }
  ]
}
// ...

How to React to treat it? They will see it as a series of changes in the type of child: children [0] of the type originally Message, but now he's Table. Because they are a reference to the function (and different functions), it will uninstall the entire Table and install it again, rendering all of its offspring: more than 1000 lines!

So, you can add a unique key (but in this particular case `key` not the best choice) or a more intelligent trick: using Boolean operations && short-circuit, which is a` JavaScript` and many other modern languages characteristic. like this:

<div>
  {isShowMessage && <Message />}
  <Table />
  <Footer />
</div>

Even if Message was closed (not show), props.children div parents will have three elements, children [0] has a value of false (Boolean). Remember the true / false, null is even undefined Virtual DOM Object type attribute allows the value of it? Browser finally get something like this:

// ...
props: {
  children: [
    false, //  isShowMessage && <Message /> 短路成了false
    { type: Table },
    { type: Footer }
  ]
}
// ...

So, regardless of whether the Message is displayed, the index will not change, and Table Table will still be compared, but usually only compare Virtual DOM DOM node from which to delete than create them much faster.

Now on to more advanced stuff. Developers like HOC. Higher-order component is a function that a component as a parameter, add some behavior, and returns a different components (function):

function withName(SomeComponent) {
  return function(props) {
    return <SomeComponent {...props} name={name} />;
  }
}

Developers have created a HOC in the parent render method. When the need to re-render tree `React`, React` the Virtual DOM is as follows:

// On first render:
{
  type: ComponentWithName,
  props: {},
}

// On second render:
{
  type: ComponentWithName, // Same name, but different instance
  props: {},
}

Now, React will only run on ComponentWithName a diff algorithm, but this time with the same name references a different instance, three equals comparison fails and must be completely re-mounted. Note that it will also lead to the loss of status, fortunately, it is easy to fix: just returned instances are the same like:

// 单例
const ComponentWithName = withName(Component);

class App extends React.Component() {
  render() {
    return <ComponentWithName />;
  }
}

Fix update

Now the browser has to ensure that things are not reloaded, unless necessary. However, any changes to the components located in the root directory of the DOM made will result in comparison to redraw all of its children. Structure complex, expensive and often avoided.

If there is a way to tell React do not see a branch, it would be good, because it has not changed.

In this way there is, it involves a function called life-cycle component of shouldComponentUpdate. React calls this method Before each call and receive a new value of props and state. Then developers can freely compare the difference between the old value and the new value, and decide whether to update components (returns true or false). If the function returns false, React will not re-render the component in question, it does not view its subcomponents.

Usually two props and a simple state of superficial comparison will suffice: if the value of the top layer of the same property, the browser will not have to update. A relatively shallow features are not `JavaScript`, but the developers themselves are many ways to implement it, in order not to repeat create the wheel, you can use [written at someone] ( Dashed / shallowequal ).

After the introduction of the shallow compared npm package, developers can write the following code:

class TableRow extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    const { props, state } = this;
    return !shallowequal(props, nextProps)
           && !shallowequal(state, nextState);
  }
  render() { /* ... */ }
}

But you do not even have to write your own code, because React in a file named React.PureComponent built-in class this feature, which is similar to React.Component, just shouldComponentUpdate has achieved a shallow props / state comparison for you.

Perhaps you would think so, replace the Component can go to PureComponent replacement. But the developers if used incorrectly PureComponent will re-render the same problem, consider the following three conditions:

<Table
    // map每次都会返回一个新的数组实例,所以每次比较都是不同的
    rows={rows.map(/* ... */)}
    // 每一次传入的对象都是新的对象,引用是不同的。
    style={ { color: 'red' } }
    // 箭头函数也一样,每次都是不同的引用。
    onUpdate={() => { /* ... */ }}
/>

The above code snippet shows the three most common anti-pattern, try to avoid them!

Proper use of PureComponent, you can in here to see all TableRow are "purified" after rendering effect.

 

But if you can not wait to use all functions of pure components, this is wrong. Were compared props and state is not free, even for the most basic components, it is not worth it: Run shallowCompare need more time than diff algorithms.

You can use this rule of thumb: the pure components for complex forms and tables, but they generally makes such as buttons or icons simple element slow.

Guess you like

Origin www.cnblogs.com/geck/p/12448702.html