7.3.1 React component performance exploration

Recommended by React Devtools

  • React V16.5.0 + (development mode)
  • The React Developer Tools V3.3.2 +

Tracking user behavior

  1. Install the Schedule package, yarn Add Schedule

  2. Embed code where you need to track

import { unstable_track as track} from 'schedule/track'

export default class Home extends Component {
    handleSubmit =e= >{
        const text = e.target.value.trim()
        // The user hits the Enter button to track
        if(e.which===13){
            track("Add TOdo",performance,now,() = >{
                this.props.onSave(text)
                if(this.props.newTodo){
                    this.setState({text:' '})}})}Copy the code

React Profiler API

  1. Profilter is in the React package.
  2. The onRender callback function returns a series of messages.

7.3.2 Optimizing Component Performance

1. PureComponent

  • Class Component optimization tool

  • The essence is a shallow comparison in the shouldComponentUpdate method

The parent component

import React  from 'react';

export default class extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            date : new Date(),
            id:1}}componentDidMount(){
        setInterval(() = >{
            this.setState({
                date:new Date()})},1000)}render(){
        return (
            <div>
                <Child seconds={id}/>
                <div>{this.state.date.toString()}</div>
            </div>)}Copy the code

We can see that the entire component needs diff. We can see that the child component does not depend on the date variable. So when the date changes, the subcomponent does not render at all, so we can use PureComponent to optimize.

class Child extends React.PureComponent {
    render(){
        return (
            <div>{this.props.seconds}</div>)}}Copy the code

2. memo

  • Function component optimization tool

  • Is a higher-order function that performs an internal comparison similar to shouldComponentUpdate

  • You can specify a comparison function

function Child({seconds}){
    return (
        <div>I am update every {seconds} seconds</div>)};export default React.memo(Child)
Copy the code

Don’t assume that all is well with a child component called React. Memo

function Father({seconds}){
    /* If you use the reacte. memo to optimise the function, it will not work. /* If you use the reacte. memo to optimise the function, it will not work. You can use useCallback for packages */

    / / before optimization
    function change() {}

    / / after optimization
    const change  = useCallback(() = >{

    },[])
    return (
        <Child change={change}></Child>)};function Child({seconds}){
    return (
        <div>I am update every {seconds} seconds</div>)};export default React.memo(Child)
Copy the code

::: Warning React.memo() can take two arguments. The first argument is the component of a pure function. The second argument is used to compare props for flushing, similar to shouldComponentUpdate(). [2]

React. Memo is equivalent to PureComponent, but it only compares props. You can also compare the old and new props by specifying a custom comparison function with the second argument. If the function returns true, the update is skipped. : : :

function Child({seconds}){
    return (
        <div>I am update every {seconds} seconds</div>)};function areEqual(prevProps, nextProps) {
    if(prevProps.seconds===nextProps.seconds){
        return true
    }else {
        return false}}export default React.memo(Child,areEqual)
Copy the code

3 Destruction of native events and timers

4. Use unchanging data structures

Data invariance is not an architecture or a design pattern, but rather an opinionated way of writing code. This forces you to think about how to organize your application data flow. In my view, data invariance is a practice that revolves around strictly unidirectional data flows.

Advantage:

  • Zero side effects
  • Immutable data objects are easier to create, test, and use
  • Easy to track changes

Case study:

class Imu extends Component { 

    state = {
       users: []
   }

   addNewUser = () = >{
       const users = this.state.users;
       users.push({
           userName: "robin".email: "[email protected]"
       });
       this.setState({users: users}); }}Copy the code

In this case, user and this.state.users are the same reference. If we change user directly, we change this.state.users directly. The react state should be immutable because setState() can replace changes you made earlier

Changing state directly causes problems:

We use shouldComponentUpdate to determine whether to rerender the component. This.state. users and nextstate. user are the same reference, so React will not rerender the UI even if the array changes

 shouldComponentUpdate(nextProps, nextState) {
    if (this.state.users ! == nextState.users) {return true;
    }
    return false;
  }
Copy the code

How can I avoid this problem

  addNewUser = () = > {
       this.setState(state= > ({
         users: state.users.concat({
           timeStamp: new Date(),
           userName: "robin".email: "[email protected]"})})); };Copy the code

Consider the following immutable approach:

Array: [].concat or […params]

Object: object.assign ({}…) Or es6 {… params}

Optimization library for variable data structures:

  1. mmutable.js

  2. react-copy-write

5. Split the file

As new features and dependencies continue to be added, not only will your project become larger, we can consider separating third-party packages; By splitting files, your browser can download resources in parallel, reducing wait times. SplitChunksPlugin

6. Dependency optimization

When optimizing your application code, it’s important to check how many libraries you’re using in your application. For example, if you’re using moment.js; This library contains many national language packs that you don’t need, so consider using the moment-locales-webpack-plugin to remove unused language packs for your final package.

Lodash, you can remove unused features with the lodash-webpack-plugin

7. React.Fragments are used to avoid redundant HTML elements

In React we have to wrap the child elements with a root element. We can do this with react.Fragment, which will not render the actual HTML element during rendering

class Comments extends React.PureComponent{
    render() {
        return (
            <React.Fragment>
                <h1>Comment Title</h1>
                <p>comments</p>
                <p>comment time</p>
            </React.Fragment>); }}// or

class Comments extends React.PureComponent{
    render() {
        return (
            <>
                <h1>Comment Title</h1>
                <p>comments</p>
                <p>comment time</p>
            </>); }}Copy the code

8. Avoid using inline function definitions in render functions

Since the function is JavaScript ({}! == {}), so when React diff checks, inline functions will always diff fail. In addition, if you use the arrow function in the JSX property, a new instance of the function is created on each rendering. This can be a lot of work for the garbage collector.

 class CommentList extends React.Component {
    state = {
        comments: [].selectedCommentId: null
    }

    render(){
        const { comments } = this.state;
        return (
           comments.map((comment) = >{
               return <Comment onClick={(e)= >{
                    this.setState({selectedCommentId:comment.commentId})
               }} comment={comment} key={comment.id}/>}}}))Copy the code

Instead of defining an inline function for props, you can define an arrow function.

default class CommentList extends React.Component {
    state = {
        comments: [].selectedCommentId: null
    }

    onCommentClick = (commentId) = >{
        this.setState({selectedCommentId:commentId})
    }

    render(){
        const { comments } = this.state;
        return (
           comments.map((comment) = >{
               return <Comment onClick={this.onCommentClick} 
                comment={comment} key={comment.id}/>}}}))Copy the code

9. Use anti-shake throttling

Throttling: To trigger once within a specified time, no matter how many actions you do in that time, I only trigger one action

Anti-shaking: Prevents events from being triggered frequently and only after the user has stopped the action, at a time after the delay

You can use lodash

import debouce from 'lodash.debounce';

class SearchComments extends React.Component {
 constructor(props) {
   super(props);
   this.state = { searchQuery: ""}; } setSearchQuery = debounce(e= > {
   this.setState({ searchQuery: e.target.value });
 }, 1000);

 render() {
   return (
     <div>
       <h1>Search Comments</h1>
       <input type="text" onChange={this.setSearchQuery} />
     </div>); }}Copy the code

10. Avoid using Index as the Key of a Map

{
    comments.map((comment, index) = > {
        <Comment 
            {..comment}
            key={index} />})}Copy the code

Using index may cause your application to display incorrectly, because the key is used when diff is used; When you delete, add, or move a list, elements with the same key are no longer the same element.

In some cases you can use index as the key

  • Lists and items are static
  • Items in the list have no IDS, and the list is never reordered or filtered
  • Lists are immutable

11. Avoid starting the state of the component with props

class EditPanelComponent extends Component {
    
    constructor(props){
        super(props);

        this.state ={
            isEditMode: false.applyCoupon: props.applyCoupon
        }
    }

    render(){
        return <div>
                    {this.state.applyCoupon && 
                    <>Enter Coupon: <Input/></>}
               </div>
    }
}
Copy the code

If you change the props without refreshing the component, the new props value will never be assigned to the applyCoupon of the state because constructor will only be called at initialization time.

The solution: can componentWillReceiveProps, props to update the status

    
    constructor(props){
        super(props);

        this.state ={
            isEditMode: false.applyCoupon: props.applyCoupon
        }
    }

    componentWillReceiveProps(nextProps){
        if(nextProps.applyCoupon ! = =this.props.applyCoupon) {
            this.setState({ applyCoupon: nextProps.applyCoupon })
        }
    }

    render(){
        return <div>{this.props.applyCoupon && 
          <>Enter Coupon: <Input/></>}</div>
    }
}

Copy the code

12. Webpack use mode

Webpack 4,mode set to Production, WebPack will use built-in optimizations

 module.exports = {
      mode: 'production'
    };
Copy the code

13. Propagates props on the DOM element

Doing so adds unknown HTML attributes, which are unnecessary

const CommentsText = props= > {
    return (
      <div {. props} >
        {props.text}
      </div>
    );
  };
Copy the code

You can set specific properties

const CommentsText = props= > {
    return (
      <div specificAttr={props.specificAttr}>
        {props.text}
      </div>
    );
};
Copy the code

14. CSS animation instead of JS animation

Animation is inevitable for a smooth and enjoyable user experience. There are many ways to animate web pages. In general, we can create animations in three ways:

CSS transition

CSS animations

JavaScript

15. CDN

CDN can transfer static content faster, from your website or mobile application faster.

16 Web Workers API attempt

  • After using Web Workers, the Web application can:

    • Run a script in a background thread separate from the main thread.
    • Perform time-consuming tasks in separate threads to prevent time-consuming tasks from blocking the user experience
  • Communication mechanism Communication between the Web Worker and the main thread after completing time-consuming tasks

    • postMessage
// sort.worker.js
export default  function sort() {
    
    self.addEventListener('message'.e= >{
        if(! e)return;
        let posts = e.data;
        
        for (let index = 0, len = posts.length - 1; index < len; index++) {
            for (let count = index+1; count < posts.length; count++) {
                if (posts[index].commentCount > posts[count].commentCount) {
                    const temp = posts[index];
                    posts[index] = users[count];
                    posts[count] = temp;
                }
            }
        }
        postMessage(posts);
    });
}
export default Posts extends React.Component{

    constructor(props){
        super(posts);
    }
    state = {
        posts: this.props.posts
    }
    componentDidMount() {
        this.worker = new Worker('sort.worker.js');
        
        this.worker.addEventListener('message'.event= > {
            const sortedPosts = event.data;
            this.setState({
                posts: sortedPosts
            })
        });
    }

    doSortingByComment = () = > {
        if(this.state.posts && this.state.posts.length){
            this.worker.postMessage(this.state.posts); }}render(){
        const posts = this.state.posts;
        return (
            <React.Fragment>
                <Button onClick={this.doSortingByComment}>
                    Sort By Comments
                </Button>
                <PostList posts={posts}></PostList>
            </React.Fragment>)}}Copy the code

17. Virtualization long list

List virtualization, or windozing, is a technique to improve performance when presenting long lists of data. This technique renders only a small portion of the rows at any given time, and can significantly reduce the time required to re-render components, as well as the number of DOM nodes created.

There are popular React libraries such as React-Window and React-Virtualized which provide several reusable components to display lists and grids and then an virtualized table.

18. Server rendering

You can refer to the last chapter, project actual combat, there are server-side rendering code click me

19. Enable Gzip compression on the Web server

20. UseMemo caches a large amount of calculated data, and useCallback caches functions to avoid repeated creation

useMemo

The idea behind useMemo is similar to that of Memo. The second argument is an array of deps, and the changes in the parameters in the array determine whether useMemo updates the callback function.

The useMemo argument is the same as the useCallback argument. The difference is that useMemo returns a cached value, while useCallback returns a function.

  • UseMemo reduces unnecessary rendering
// Lists wrapped with useMemo can be restricted to updating the list if and only if the list changes, so that the list can be avoided recycling
 {useMemo(() = > (
      <div>{
          list.map((i, v) => (
              <span
                  key={v} >
                  {i.patentName} 
              </span>
          ))}
      </div>
), [list])}

Copy the code
  • UseMemo reduces the rendering times of subcomponents
 useMemo(() = >({/* Reduced rendering of PatentTable component */ }
        <PatentTable
            getList={getList}
            selectList={selectList}
            cacheSelectList={cacheSelectList}
            setCacheSelectList={setCacheSelectList} />
 ), [listshow, cacheSelectList])
Copy the code
  • UseMemo avoids a lot of unnecessary computing overhead

const Demo=() = >{
  /* Use useMemo to wrap the log function in useMemo to avoid redeclaring each component update, and to limit context execution */
    const newLog = useMemo(() = >{
     const log =() = >{
           // Calculate a lot
           // There is no way to get other values in real time
        }
        return log
    },[])
    // or
   constLog2 = useMemo (()=>{// Calculate a lot
        
        return // The calculated value
    },[list])
    return <div onClick={()= >newLog()} >{log2}</div>
}
Copy the code

useCallback

Both useMemo and useCallback receive the same parameters, and are executed only when the dependency changes. UseMemo returns the result of a function run, and useCallback returns the function; When a parent component passes a function to a child component, the function component generates new props each time. This causes the function to change each time it is passed to the child component. This can trigger updates to the child component, some of which are unnecessary.


const Father=({ id }) = >{
    const getInfo  = useCallback((sonName) = >{
          console.log(sonName)
    },[id])
    return <div>{/* Click the button to trigger the parent component update, but the child component is not updated */}<button onClick={() = >SetNumber (number+1)} > increment</button>
        <DemoChildren getInfo={getInfo} />
    </div>
}

/ * the react. Memo * /
const Children = React.memo((props) = >{
   /* Only when the child component is initialized is printed */
    console.log('Child Component Update',props.getInfo())
   return <div>Child components</div>
})

Copy the code

The useCallback must be compatible with the React. Memo pureComponent, otherwise it will not improve performance and may degrade performance.

The react-hooks are not meant to replace the class-declared components completely. For complex components, class components are preferred, but we can separate class components into funciton components, which are responsible for logical interaction and which need dynamic rendering according to business requirements. Then with usememo and other apis, to improve performance. There are also restrictions on react-hooks use, such as they cannot be placed in process control statements, and execution context requirements.

21. Lazy initialization

Before optimization:

function table(props) {
    const [state,setState]=useState(createRows(props.count))
}
// This will cause createRows to be invoked every time a component is updated
const values = createRows(props.count)
const [state,setState]=useState(values)

Copy the code

After the optimization:

function table(props) {
    const [state,setState]=useState(() = >{
       return createRows(props.count)
    })
}
Copy the code

conclusion

It is recommended to benchmark and measure performance first. Consider using the Chrome Timeline analysis and visualization component. You can see which components have been uninstalled, installed, updated, and how much time they have spent relative to each other. It will help you begin the journey of performance tuning.

Complete documentation: hejialianghe. Gitee. IO/react/react…