• React@16.x(15)PureComponent 和 memo


    1,什么是 PureComponent

    纯组件,为了避免不必要的渲染(运行 render)来提升效率。

    优化思路:如果一个组件的状态和属性都没有变化,那就不用重新渲染。

    具体实现:当某个组件 extends PureComponent 时,则该组件的 shouldComponentUpdate 中会对新旧属性和状态进行浅比较,如果都相等则不会重新渲染(return false)。

    // 原理大致如下:
    import React, { Component } from "react";
    
    export default class Task extends Component {
        shouldComponentUpdate(nextProps, nextState) {
            if (isEqual(this.props, nextProps) && isEqual(this.state, nextState)) {
                return false;
            }
            return true;
        }
        render() {}
    }
    
    const isEqual = (obj1, obj2) => {
        for (const key in obj1) {
           if (obj1[key] !== obj2[key]) {
                return false
           }
        }
        return true
    }
    

    效果等于:

    import React, { PureComponent } from "react";
    
    export default class Task extends PureComponent {
        render() {}
    }
    

    2,什么是 memo

    React.memo 是一个HOC,相当于给函数组件套了一个 PureComponent,让函数组件也能进行优化。

    // 大致原理:
    import React, { PureComponent } from "react";
    
    function memo(funcComp) {
        return class Memo extends PureComponent {
            render() {
                return <>{funcComp(this.props)}</>
            }
        };
    }
    

    3,举例

    一个展示列表的组件,逻辑很简单,结构如下:

    -- TaskContainer
    	-- TaskList
    		-- Task
    	-- TaskAdd(一个输入框,可以新增列表项)	
    

    TaskContainer(console.log("container render");

    import React, { Component } from "react";
    import TaskAdd from "./TaskAdd";
    import TaskList from "./TaskList";
    
    export default class TaskContainer extends Component {
        state = {
            list: Array.from(new Array(10)).map((item, index) => `任务${index}`),
        };
        
        render() {
            console.log("container render");
            return (
                <div>
                    <TaskAdd
                        changeList={(newTask) => {
                            this.setState({
                                list: [...this.state.list, newTask],
                            });
                        }}
                    ></TaskAdd>
                    <TaskList list={this.state.list}></TaskList>
                </div>
            );
        }
    }
    

    TaskList(console.log("list render");

    import React, { Component } from "react";
    import Task from "./Task";
    
    export default class TaskList extends Component {
        render() {
        	console.log("list render");
            return (
                <div>
                    {(this.props.list || []).map((m) => (
                        <Task name={m} key={m}></Task>
                    ))}
                </div>
            );
        }
    }
    

    TaskAdd(console.log("add render");

    import React, { Component } from "react";
    
    export default class TaskAdd extends Component {
        state = {
            taskName: "",
        };
    
        render() {
            console.log("add render");
            return (
                <div>
                    <input
                        value={this.state.taskName}
                        onChange={(e) => {
                            this.setState({ taskName: e.target.value });
                        }}
                    ></input>
                    <button onClick={() => this.props.changeList(this.state.taskName)}>添加任务</button>
                </div>
            );
        }
    }
    

    Task(console.log("task render");

    import React, { Component } from "react";
    
    export default class Task extends Component {
        render() {
            console.log("task render");
            return <div>{this.props.name}</div>;
        }
    }
    

    使用:

    import React, { Component, Component } from "react";
    import TaskContainer from "./components/Pure/TaskContainer";
    
    export default class App extends Component {
        render() {
            return <TaskContainer></TaskContainer>;
        }
    }
    

    初次渲染时,打印结果:

    container render
    add render
    list render
    10次 task render
    

    添加一个列表项时,打印结果:

    container render
    add render
    list render
    11次 task render
    

    3.2,优化1

    当添加一个列表项时,已经被渲染的10个 task 不应该再次渲染,因为它们自身没有发生任何变化。只应该渲染新增的那一个即可。

    此时就可以用到 PureComponent

    export default class Task extends Component {}
    // 替换为
    export default class Task extends PureComponent {}
    

    这时再添加一个列表项时,在打印结果可以看到 Task 只渲染了一次(新增的)。

    container render
    add render
    list render
    task render
    

    3.1,优化2-函数位置

    注意到新增列表时,TaskAdd 组件在点击按钮后,不应该再次渲染(不应该打印 add render),因为它的状态和属性也没有发生变化。

    也做下替换:

    export default class TaskAdd extends Component {}
    // 替换为
    export default class TaskAdd extends PureComponent {}
    

    发现结果并没有发生变化,add render 依旧会打印。

    这时因为在 TaskContainer 中,给组件 TaskAdd 传递的属性 changeList 是直接写在组件上的。这样每次执行 TaskContainer.render() 时,都算作一个新的属性,所以 TaskAdd extends PureComponent 没有生效。

    做以下替换即可:

    export default class TaskContainer extends Component {
        render() {
            return (
                <div>
                    <TaskAdd
                        changeList={(newTask) => {
                            this.setState({
                                list: [...this.state.list, newTask],
                            });
                        }}
                    ></TaskAdd>
                </div>
            );
        }
    }
    
    // 替换为
    export default class TaskContainer extends Component {
        changeList = (newTask) => {
            this.setState({
                list: [...this.state.list, newTask],
            });
        };
    
        render() {
            return (
                <div>
                    <TaskAdd changeList={this.changeList}></TaskAdd>
                </div>
            );
        }
    }
    

    4,注意点

    4.1,为了提升效率,应该尽量使用 PureComponent

    4.2,不要直接改变之前的状态,而是覆盖

    旧的状态应该是不可变的(immutable),只能通过创建新状态来覆盖之前的状态。

    举例,当使用 PureComponent 时,下面的代码不会更新数据。因为进行的是浅比较,此时数组地址相同。

    this.state.task.push(xxx)
    this.setState({
    	task: this.state.task
    })
    

    换成下面的方式就可以了:产生了新数组。

    this.setState({
    	task: [...this.state.task, xxx]
    })
    // 或
    this.setState({
    	task: this.state.task.concat(xxx)
    })
    

    如果是对象的话:产生了新对象。

    this.setState({
    	obj: {
    		...this.state.obj,
    		b: xxx
    	}
    })
    // 或
    this.setState({
    	obj: Object.assign({}, this.state.obj, {b: xxx})
    })
    

    4.3,为什么不进行深比较?

    因为本来就是为了提升效率,减少 render 执行次数。深比较会比较费时,得不偿失。


    以上。

  • 相关阅读:
    javaee springMVC Rest风格和Ant风格
    Python编程中的异常处理
    数据结构(二)
    [PAT-Advanced] A1054. The Dominant Color (20)
    嵌入式学习笔记(63)指针到底是什么
    Docker基本配置及使用
    JAVA计算机毕业设计大学生兼职招聘网站计算机(附源码、数据库)
    网络——ARP、DHCP、ICMP协议
    Android MediaCodec message机制分析
    vue执行配置选项npm run serve的本质
  • 原文地址:https://blog.csdn.net/qq_40147756/article/details/139130953