阅读下面的代码,请回答 render 总共执行了多少次?这个问题涉及到react的生命周期。
代码如下
import React, { Component, PureComponent } from 'react' /** * 组件有两个状态,一个是li,另一个是添加li */ interface ITestPureComponentS { lis: number[], addLi: (li: number) => void } // 测试li 组件 class TestLi extends Component { render() { console.log('TestLi render'); return ( <li>{this.props.children}</li> ) } } export default class TestPureComponent extends Component<{}, ITestPureComponentS> { state = { lis: [], addLi: (li: number) => { this.setState({ lis: [...this.state.lis, li] }) } } // 组件即将渲染的时候,添加10个li componentDidMount() { let lis: number[] = [] for (let i = 1; i <= 10; i++) { lis.push(i) } this.setState({ lis: lis }) } /** * 添加一个li */ addLis = () => { const num = Date.now() this.state.addLi(num) } render() { console.log('TestPureComponent render'); const lisD = this.state.lis.map(i => { return ( <TestLi key={i}>{i}</TestLi> ) }) return ( <div> {lisD} <button onClick={this.addLis}>添加一个li</button> </div> ) } }
分析
- app 组件: 这里只是直接挂载,没有做任何操作,所以render 只会执行一次
- TestPureComponent组件: 组件挂载阶段, 是需要执行一次的,然后组件挂载完毕后,我们手动setState({}) ,调用这个方法是会触发组件的render()函数的。 然后它还要在执行一次,所以这个组件的render() 总共执行两次
- TestLi组件: 在父组件被render的同时子组件也会调用render(), 我们这里是直接使用 this.state.lis 的,一开始是空的,里面是没有 TestLi 组件的. 所以该组件执行的次数 应该为 10 次
打印结果
如果可以答对的网友,恭喜你,对react 的生命周期还是蛮清楚的。加油,继续往下
点击,添加一个li render 又会执行多少次?
分析
- TestPureComponent 组件: 点击按钮会触发 this.setState() 所以,TestPureComponent 组件必然会执行 render() ,该组件会执行 render 1次
- TestLi组件 由于父组件执行render 并且数据发生变化,里面的值多了一个,所以改组件会执行 11 次
效果
注意: 我们只是加了一个数据,并且数据都没有变化,为啥要渲染11次呀,这个render 虽然只是一点点的性能,如果组件树大了,那么render的次数也会相应的变得庞大。这个是非常影响性能的。所以有没有啥优化方法?
优化方法一: 代码结构优化,我们不在render()这里 进行map遍历,而是直接在componentDidMount的时候直接生成一个TestLi组件的数组,然后改变数据就不会重新渲染了, 如果网友你能想到这个问题?那确实你react 方面掌握的是非常厉害的。本人是刚刚误打误撞发现的。但是在大部分同学写代码都不怎么能优化,所以选择优化方法二;
优化方法二:shouldComponentUpdate(nextProps, nextState) 这里面能够直接决定是否需要重新 执行 render, 所以优化的方法就在这个路口,优化的思路也是比较容易的,将传递过来的state 和 props 和当前的 state, props进行比较,如果变化了,那么直接return true, 否则 return false;
在TestLi组件中进行优化
// 测试li 组件 class TestLi extends Component { /** * 浅判断 * @param obj * @param obj2 * @returns */ objectIsEqual(obj: any, obj2: any) { for (const key in obj) { if (Object.is(obj[key], obj2[key])) { return true; } } return false } shouldComponentUpdate(nextProps: {}, nextState: {}) { if (this.objectIsEqual(nextProps, this.props)) { return false; } return true } render() { console.log('TestLi render'); return ( <li>{this.props.children}</li> ) } }
效果
注意: 有的网友又要问了,为啥要使用浅比较 而不进行深比较,个人决定浅比较不怎么消耗内存,省时省力。如果深度比较的话,那么就会比较消耗内存了,并且我们是在做优化操作,不需要进行深度比较。
PureComponent
PureComponent 就是上面TaskLi 里面封装的一个组件,改组件继承component, 里面的实现思路也是一样的,都是在shouldComponentUpdate 这里进行操作的,也是使用的是浅比较
代码改写成这样,也是可以实现一样的效果
// 测试li 组件 class TestLi extends PureComponent { render() { console.log('TestLi render'); return ( <li>{this.props.children}</li> ) } }
注意
1.pureComponent 是纯组件,进行的是浅比较,因此数据如果涉及到引用类型的,如对象或者数组等,需要重新创建一个,不然是无法进行数据更新的。为了效率,我们应该使用这个组件
2.要求不改变之前的状态,需要对状态进行覆盖(Immutable)这个公共库可以避免我们犯错,具体的请自行查看。
3.如果函数组件需要使用纯组件,需要使用 React.memo 制作村组件,这个memo就是一个高阶组件,里面返回的还是一个类组件。有兴趣的自己编写