React@16.x(15)PureComponent 和 memo

目录

1,什么是 PureComponent

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

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

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

js 复制代码
// 原理大致如下:
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
}

效果等于:

js 复制代码
import React, { PureComponent } from "react";

export default class Task extends PureComponent {
    render() {}
}

2,什么是 memo

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

js 复制代码
// 大致原理:
import React, { PureComponent } from "react";

function memo(funcComp) {
    return class Memo extends PureComponent {
        render() {
            return <>{funcComp(this.props)}</>
        }
    };
}

3,举例

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

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

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

js 复制代码
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");

js 复制代码
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");

js 复制代码
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");

js 复制代码
import React, { Component } from "react";

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

使用:

js 复制代码
import React, { Component, Component } from "react";
import TaskContainer from "./components/Pure/TaskContainer";

export default class App extends Component {
    render() {
        return <TaskContainer></TaskContainer>;
    }
}

初次渲染时,打印结果:

txt 复制代码
container render
add render
list render
10次 task render

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

txt 复制代码
container render
add render
list render
11次 task render

3.2,优化1

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

此时就可以用到 PureComponent

js 复制代码
export default class Task extends Component {}
// 替换为
export default class Task extends PureComponent {}

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

txt 复制代码
container render
add render
list render
task render

3.1,优化2-函数位置

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

也做下替换:

js 复制代码
export default class TaskAdd extends Component {}
// 替换为
export default class TaskAdd extends PureComponent {}

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

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

做以下替换即可:

js 复制代码
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 时,下面的代码不会更新数据。因为进行的是浅比较,此时数组地址相同。

js 复制代码
this.state.task.push(xxx)
this.setState({
	task: this.state.task
})

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

js 复制代码
this.setState({
	task: [...this.state.task, xxx]
})
// 或
this.setState({
	task: this.state.task.concat(xxx)
})

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

js 复制代码
this.setState({
	obj: {
		...this.state.obj,
		b: xxx
	}
})
// 或
this.setState({
	obj: Object.assign({}, this.state.obj, {b: xxx})
})

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

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


以上。

相关推荐
jacy1 分钟前
图片大图预览就该这样做
前端
林太白3 分钟前
Nuxt3 功能篇
前端·javascript·后端
YuJie4 分钟前
webSocket Manager
前端·javascript
Mapmost20 分钟前
Mapmost SDK for UE5 内核升级,三维场景渲染效果飙升!
前端
Mapmost22 分钟前
重磅升级丨Mapmost全面兼容3DTiles 1.1,3DGS量测精度跃升至亚米级!
前端·vue.js·three.js
wycode29 分钟前
Promise(一)极简版demo
前端·javascript
浮幻云月30 分钟前
一个自开自用的Ai提效VsCode插件
前端·javascript
DevSecOps选型指南31 分钟前
SBOM风险预警 | NPM前端框架 javaxscript 遭受投毒窃取浏览器cookie
前端·人工智能·前端框架·npm·软件供应链安全厂商·软件供应链安全工具
__lll_39 分钟前
Docker 从入门到实战:容器、镜像与 Compose 全攻略
前端·docker