React更新机制
- React渲染流程: JSX -> 虚拟DOM -> 真实DOM
- React更新流程: props/state改变 -> render函数重新执行 -> 产生新的DOM树 -> 新旧DOM树进行diff -> 计算出差异进行更新 -> 更新到真实的DOM
render函数被调用
只要修改了App中的数据,所有组件都需要重新render,进行diff算法,性能低,实际上,很多组件没有必要重新render,调用render应该有一个前提,就是依赖的数据(state,props)发生改变,再调用自己的render方法。 如何控制render函数是否被调用?
- 通过shouldComponentUpdate()
React的更新流程
- React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树。
- React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI
- 如果两颗树进行完全比较更新,React的更新性能会非常低效
- 于是,React对这个算法进行优化,将其优化成O(n)
- 即,同层节点之间互相比较,不会垮节点比较
- 不同类型的节点,产生不同的树结构
- 通过key来指定哪些节点在不同的渲染下保持稳定
key的优化
- 在最后位置插入数据(有无key意义不大)
- 在前面插入数据(在没有key的情况下,所有的li都需要进行修改)
- 当子元素(li)拥有key时,React使用key来匹配原有树上的子元素以及最新树上的子元素,位移即可,无需修改
- key应该是唯一的
- key不要使用随机数(随机数在下一次render时,会重新生成一个数字)
- 使用index作为key,对性能是没有优化的
shouldComponentUpdate简称SCU
- 参数1:newProps,修改之后,最新的props属性
- 参数2:newState,修改之后,最新的state属性
- 参数3: context
该方法返回值是boolean类型
- 返回值为true,调用render()
- false,不调用render()
- 默认返回true,即只要state发生改变,就会调用render()
PureComponent、memo
- 如果通过props或state中数据是否改变,来决定shouldComponentUpdate返回true或false,比较麻烦
- 所以React已经默认帮我们实现好了,将class继承自PureComponent
import React, { PureComponent } from "react";
import Home from "./Home";
import Recommend from "./Recommend";
import Profile from "./Profile";
export class App extends PureComponent {
constructor() {
super();
this.state = {
msg: "zm6",
count: 99,
};
}
// 如果不使用Component,App进行性能优化
// react-dom.development.js:86 Warning: App has a method called shouldComponentUpdate().
// shouldComponentUpdate should not be used when extending React.PureComponent. Please extend React.Component if shouldComponentUpdate is used.
// shouldComponentUpdate(newProp, newState) {
// if (this.state.msg !== newState.msg || this.state.count !== newState.count) {
// return true;
// }
// return false;
// }
changeText() {
this.setState({
msg: "changed msg",
});
}
add() {
this.setState({
count: this.state.count + 1,
});
}
render() {
console.log("AppRender=> ");
const { msg, count } = this.state;
return (
<div>
<h2>
App-{msg}-{count}
</h2>
<button onClick={(e) => this.changeText()}>changeText</button>
<button onClick={(e) => this.add()}>+1</button>
<Home msg={msg} />
<Recommend count={count} />
<Profile msg={msg} />
</div>
);
}
}
export default App;
import React, { PureComponent } from "react";
export class Home extends PureComponent {
render() {
console.log("HomeRender=>");
return (
<div>
<h2>Home-{this.props.msg}</h2>
</div>
);
}
}
export default Home;
import React, { PureComponent } from "react";
export class Recommend extends PureComponent {
render() {
console.log("RecommendRender=>");
return (
<div>
<h2>Recommend-{this.props.count}</h2>
</div>
);
}
}
export default Recommend;
import { memo } from "react";
const Profile = memo(function (props) {
console.log("ProfileRender=> ");
return <div>Profile-{props.msg}</div>;
});
export default Profile;
数据不可变的力量
import React, { PureComponent } from "react";
export class App extends PureComponent {
constructor() {
super();
this.state = {
books: [
{ name: "js高级程序设计", price: 1, count: 10 },
{ name: "js重难点", price: 2, count: 20 },
{ name: "你不知道的js", price: 4, count: 30 },
{ name: "Vue设计与实现", price: 7, count: 40 },
],
};
}
addCount(index) {
const books = [...this.state.books];
books[index].count += 1;
this.setState({
books: books,
});
}
addNewBook() {
const newBook = { name: "Nest从入门到入土", price: 999, count: 1000 };
// 1.直接修改原有的state,重新设置一遍
// 在PureComponent是不能引起重新渲染的
// this.state.books.push(newBook);
// this.setState({
// books: this.state.books,
// });
// 2.浅拷贝一份books,在新的books中修改,设置新的books,因为是浅层比较,所以会重新渲染
const books = [...this.state.books];
books.push(newBook);
this.setState({
books,
});
}
render() {
const { books } = this.state;
return (
<div>
<h2>data-list</h2>
<ul>
{books.map((item, index) => {
return (
<li key={index}>
<span>
name:{item.name}-price:{item.price}-count:{item.count}
</span>
<button onClick={(e) => this.addCount(index)}>+1</button>
</li>
);
})}
</ul>
<button onClick={(e) => this.addNewBook()}>add new book</button>
</div>
);
}
}
export default App;