上节课我们学习了关于context
的使用,但是实际上呢我们在开发中几乎不用哪个东西,我们基本只在封装react
插件的时候才会用。这节课呢我们来讲关于组件优化相关的东西
回顾
在开始这节课的内容之前,我们先对上节课的内容来做一个简单的回顾:
context
可以让祖组件
和后代组件
之间通信- 通过
React.createContext
方法创建Context
组件 - 通过
<Xxxxx.Provider value={{key:value}}>
包裹组件标签来给组件以及其子组件传递context
- 通过在组件中声明
static contextType
来接收context
,但是只适用于类组件 - 通过
<Xxxxx.Consumer>
标签包裹函数来使用context
的数据,还方法使用与所有组件
以上便是上节课的内容,接下来我们来开始这节课的学习
案例
我们先通过一个案例来引出我们这节课的内容
export default class Parent extends Component { state = { sum: 0 } render() { return ( <div className="parent"> <h3>Parent Component</h3> <span>sum: {this.state.sum}</span><br> <button onClick={this.handleClick}>click me</button> <Child /> </div> ); } handleClick = () => this.setState(state => ({ sum: state.sum + 1 })); } class Child extends Component { render() { return ( <div className="child"> <h3>Child Component</h3> </div> ); } }
我们来看着段代码,还是点击加一的案例,这段代码中我们写了两个组件,并且形成父子关系,然后我们加了一点样式,我们看一下效果:
这就是我们这个效果,可以看出两个组件的层级关系。而且点击也没有问题,但是现在我们要把这个sum
传给子组件展示,是不是可以通过props
传给子组件?
export default class Parent extends Component { state = { sum: 0 } render() { const {sum} = this.state; return ( <div className="parent"> <h3>Parent Component</h3> <span>sum: {sum}</span><br/> <button onClick={this.handleClick}>click me</button> <Child sum={sum}/> </div> ); } handleClick = () => this.setState(state => ({ sum: state.sum + 1 })); } class Child extends Component { render() { return ( <div className="child"> <h3>Child Component</h3> <span>{this.props.sum}</span> </div> ); } }
我们现在把sum
传给了子组件,而且在子组件中也做了展示,那么问题来了,我们点击按钮的话,子组件中的sum
会不会变呢?我们来看一下:
我们来看,子组件中的数据是不是也跟着变了?为什么?我们点击按钮是不是会改state
?改了state
是不是要重新渲染页面?那么重新渲染的时候是不是会把新的state
通过props
传给子组件?所以说子组件也会变。
但是我们现在有一个问题,我现在不给子组件传props
,我们来看代码:
export default class Parent extends Component { state = { sum: 0 } render() { console.log("Parent------"); const {sum} = this.state; return ( <div className="parent"> <h3>Parent Component</h3> <span>sum: {sum}</span><br/> <button onClick={this.handleClick}>click me</button> <Child /> </div> ); } handleClick = () => this.setState(state => ({ sum: state.sum + 1 })); } class Child extends Component { render() { console.log("Child------"); return ( <div className="child"> <h3>Child Component</h3> </div> ); } }
我们现在已经不给Child
组件传递props
了,那么我们点击按钮的时候state
改变,父组件的render
调用,那么子组件的render
会调用吗?我们来看一下:
是不是每次点击按钮两个组件的render
都被调用了啊?那么这个情况是不是不太合理?我们子组件根本没有用父组件的东西,凭什么还调用render
呢?那么我们再看这种情况:
export default class Parent extends Component { state = { sum: 0 } render() {...} handleClick = () => this.setState({}); } class Child extends Component {...}
这是什么意思?是不是我们修改state
,但是实际上啥都没改?那么我们再看一下结果:
实际上的结果呢就是虽然我们没有修改任何状态,但是我们的这两个组件的render
还是都被调用了,这很不合理啊,按照理论上来说我们是不是期望当state
和props
真正有改变的时候才重新调用render
?但是现在啥都没变还调用render
这就很不合理,一部分效率算是被白白浪费掉了。所以我们得出两个结论:
Component
中只要调用了setState
,不管state
有没有改变都会重新调用render
Component
中的render
只要被重新调用就会导致子组件的render
重新调用
但是这是为什么呢?因为shouldComponentUpdate
这个生命周期钩子一直返回的都是true
,可是我们不想要上面的这种结果,我们只想要state
或者props
发生改变了之后再调render
那么我们应该怎么处理呢?
export default class Parent extends Component { state = { sum: 0 } shouldComponentUpdate(nextProps, nextState) { console.log(nextProps, nextState); console.log(this.props, this.state); } render() { console.log("Parent------"); const { sum } = this.state; return ( <div className="parent"> <h3>Parent Component</h3> <span>sum: {sum}</span><br /> <button onClick={this.handleClick}>click me</button> </div> ); } handleClick = () => this.setState(state => ({sum: state.sum + 1})); }
我们来看这段代码,我们先不渲染Child
组件,我们说了原因是shouldComponentUpdate
一直返回的都是true
,那么我们就现在这个钩子上做点文章,其实这个钩子是可以接收到两个参数的,一个是下一次的props
另一个是下一次的state
,那么我们来打印出来这俩参数,以及我们当前的state
是什么:
首先,我们组件自身没有props
,所以说当前和下一次的props
都是空对象,而state
我们发现了当前的state
和下一次的state
不一样,那么这个时候我们是不是应该返回true
来让组件更新?那么我们应该这么处理呢?
export default class Parent extends Component { state = { sum: 0 } shouldComponentUpdate(nextProps, nextState) { if (this.state.sum !== nextState.sum) { return true } return false } render() { console.log("Parent------"); const { sum } = this.state; return ( <div className="parent"> <h3>Parent Component</h3> <span>sum: {sum}</span><br /> <button onClick={this.handleClick}>click me</button> </div> ); } handleClick = () => this.setState({}); }
我们来看这段代码,我们是不是判断了当前的state
中的sum
属性和下一次state
的sum
属性是不是相同,如果不同才返回true
,否则返回false
,那么这次我们让setState
不修改任何数据,那么我们来看一下:
这一次,无论我们怎么点击,state
是不是压根没有变?没有变的话钩子返回false
,那么我们的render
是不是就不会重新调用了?那么我们再来说一下Child
组件:
export default class Parent extends Component { ... render() { console.log("Parent------"); const { sum } = this.state; return ( <div className="parent"> ... <Child sum={0} /> </div> ); } ... } class Child extends Component { shouldComponentUpdate(nextProps) { if (this.props.sum !== nextProps.sum) { return true } return false } render() { console.log("Child--------"); return ( <div className="child"> <h3>Child Component</h3> <span>{this.props.sum}</span> </div> ); } }
我们来看这段代码,我们是不是也是一样的道理?我们判断props
和nextProps
,但是我们这次给Child
传一个固定值作为props
那么我们来看一下结果:
我们现在来看,我们的父组件的state
变了,但是子组件的props
并没有变,所以说一直在调用Parent
组件的render
方法,但是Child
组件的render
方法值在第一次加载的时候渲染了一次。
PureComponent
现在我们是不是已经实现了我们之前所说的要求,只有在state
或者props
发生改变了再重新调用render
,那么大家再来思考一个问题,如果我state
或者props
里面有很多属性怎么办呢?我们还一个一个属性去判断吗?
当然不是,其实react
里面给我提供了一个东西叫PureComponent
,我们以后再要定义类组件就不再继承自Component
了,那么我们继承什么呢?我们继承PureComponent
这个东西不需要我们自己写shouldComponentUpdate
钩子,他会自己去判断,我们来试一下:
import React, { PureComponent } from 'react'; import './index.css'; export default class Parent extends PureComponent { state = { sum: 0 } render() { console.log("Parent------"); const { sum } = this.state; return ( <div className="parent"> <h3>Parent Component</h3> <span>sum: {sum}</span><br /> <button onClick={this.handleClick}>click me</button> <Child sum={0} /> </div> ); } handleClick = () => this.setState(state => ({ sum: state.sum + 1 })); } class Child extends PureComponent { render() { console.log("Child--------"); return ( <div className="child"> <h3>Child Component</h3> <span>{this.props.sum}</span> </div> ); } }
我们现在把我们之前写的生命周期钩子都删掉了,那么我们还是给Child
传一个固定的值:那么我们看一下:
我们来看,是不是依然只调用了Parent
组件的render
,那么我们在来试一下让setState
啥也不修改:
这一次则不再调用任何render
。但是PureComponent
做的也是浅对比,和之前redux
里面一样。
总结
PureComponent
自动改写了shouldComponentUpdate
钩子PureComponent
底层用的是浅对比
Copyright statement:The articles of this site are all original if there is no special explanation, indicate the source please when you reprint.
Link of this article:https://work.lynchow.com/article/extend_pure_component/