无用渲染(react数据更新的问题)
组件有自己的状态state, 当state的值由于用户的操作而发生改变时,即使state每次改变的都是相同的值,但调用setState方法组件就会重新渲染。在项目中,一个组件可能会被频繁地进行渲染,这些渲染虽然有一小部分是必须的,不过大多数都是无用的,它们的存在会大大降低应用的性能。
修改state时,每次都修改相同的值
// App.js
import React from "react";
import Des from "./des";
class App extends React.Component {
render() {
return (
<div>
<Des></Des>
</div>
)
}
}
export default App;
// Des.js
import React, { Component } from 'react'
export default class Des extends Component {
state={
num:1
}
change=()=>{
this.setState({num:20});
}
render() {
console.log("render运行,页面重新渲染");
return (
<div>
<p>{this.state.num}</p>
<button onClick={this.change}>change</button>
</div>
)
}
}
第一次修改了数据页面刷新,render函数运行。而之后数据没有发生变化,每次都是修改相同的值,希望的到的结果是页面不需要重新渲染,不需要运行render函数。但实际情况是只要调用this.setState方法都会让render函数再次运行,这就是无用的渲染就使得性能降低。
Vue是在get和set里触发更新,Vue在get部分有一个重要的操作就是依赖收集。这样当更改了数据后,只会更新用到了这个数据的地方,这样做到最小的更新范围。
React的更新是调用方法时触发的,并没有依赖收集的过程。所以React会更新整个组件树,也就是会把子组件一起更新即使更新的数据和子组件没有任何关系。
// App.js
import { Component } from 'react'
import appStyle from './App.module.css'
import Son from './son.js'
class App extends Component {
constructor(props) {
super(props)
this.state = {
msg: "hello App"
}
console.log("constructor 运行")
}
changeHandle = () => {
this.setState({
msg: "呼神护卫"
})
}
render() {
console.log("App render 运行")
return (
<div>
<h1>App组件</h1>
<button className={appStyle["complex-button"]} onClick={this.changeHandle}>修改state</button>
<Son></Son>
</div>
)
}
}
export default App;
// son.js
import React from "react";
class Son extends React.Component {
render() {
console.log("son render 运行")
// 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
return (
<div>
<h6>son 组件</h6>
</div>
)
}
}
export default Son;
类组件可以使用shouldComponentUpdate和PureComponent来解决无用的渲染。
PureComponent
React在进行组件更新时,如果发现它一个继承PureComponent的组件,React就会将这个组件现在的所有state和props和修改后这个组件新的state和props进行比较,如果它们的值没有变化。就不会运行render函数,重新渲染页面。
让组件成为PureComponent组件,只需要继承
React.PureComponent
// Des.js
import React, { PureComponent } from 'react'
export default class Des extends PureComponent {
state={
num:1
}
change=()=>{
this.setState({num:20});
}
render() {
console.log("render运行,页面重新渲染");
return (
<div>
<p>{this.state.num}</p>
<button onClick={this.change}>change</button>
</div>
)
}
}
第一次修改了数据页面刷新,render函数运行。而之后数据没有发生变化,每次都是修改相同的值,现在的结果是页面没有重新渲染,也没有运行render函数。
使用PureComponent组件的注意事项
当组件为PureComponent时,在修改对象、数组等引用类型的数据就需要注意一个问题:修改引用数据的值时,页面可能不会渲染。
// App.js
import { PureComponent } from 'react'
class App extends PureComponent {
state = {
msg:{
name:"启示录",
singer:"G.E.M."
},
arr: ["I","Am","Gloria","Word","Tour"]
}
changeState = () => {
this.state.arr.push("yyds");
this.state.msg.name = "摩天动物园";
this.setState(this.state);
console.log(this.state.arr);
console.log(this.state.msg);
}
render() {
console.log("组件渲染了");
return (
<div>
<div>{this.state.arr}</div>
<h1>{this.state.msg.name}</h1>
<button onClick={this.changeState}>修改state</button>
</div>
)
}
}
export default App;
多次点击修改state按钮,从控制台输出结果是state的值已经被修改,但是页面并没有被重新渲染。
原因是:PureComponent执行的是浅比较。对于引用类型的数据而言是比较对象的引用地址是否发生改变。
在PureComponent组件中,react的state里面的引用类型,更改时必须赋予一个新对象,也就是引用地址必须要变。
解决方法
import { PureComponent } from "react";
export default class Home extends PureComponent {
state = {
msg:{
name:"启示录",
singer:"G.E.M."
},
arr: ["I","Am","Gloria","Word","Tour"]
}
changeState = () => {
// 赋予新数组
this.state.arr = [...this.state.arr,"yyds"];
this.state.msg = {
...this.state.msg,
name:"摩天动物园"
}
this.setState(this.state);
console.log(this.state.arr);
console.log(this.state.msg);
}
render() {
console.log("组件渲染了");
return (
<div>
<div>{this.state.arr}</div>
<h1>{this.state.msg.name}</h1>
<button onClick={this.changeState}>修改state</button>
</div>
)
}
}
Purecomponent下对于对象和数组的修改总结:因为PureComponent会根据state是否改变来决定是否更新,而对于对象数组这样的引用类型判断它是否改变的原理是看它的内存地址,而不是内容。所以PureComponent下修改对象和数组,一定要赋予一个新对象。一般不直接操作原对象,而是先浅拷贝一份,在进行操作。