一, React 的渲染机制
要掌握一两项 React-render 优化的方法不难, 但是非常重要. 无论是在实际项目中的一个小细节, 还是迎合'面试官'的口味
1.1 触发 Render
我们知道 React 要更新视图, 必须要触发 Render. 而这往往是影响性能最重要的一步 (因为操作了 dom). 而 React 之所以这么出色, 正是因为占其主导地位的 diff 算法采用了虚拟 DOM(React V-dom), 使得渲染性能大大提升.
即便如此, 我们在开发的时候也应该要注意一些性能的优化, 比如避免无意义的 render
那么, 触发 render 的条件有哪些呢?
首次加载组件
使用了 setState(更新了 Props)
Props 更新了 (父级传给子级的值改变了)
我们完全可以避免 2.3 情况导致的一些性能问题
1.2 React Diff
虽然说 React 高效的 Diff 算法完美结合了虚拟 DOM, 让用户可以'无限制'刷新而不需要考虑任何性能问题.
但 diff 算法还是需要一定的时间, 如果你不在意触发 render 的细节, 项目模块大了起来, 自然就会影响性能了.
1.3 不做任何优化的例子
我尝试着从实现以下功能:
不更新 state 的值, 但是调用了 setState 方法
传给子级的值不改变, 即子级 props 实际上是没变化的
- // 父级. JS
- import React from 'react'
- import MockChild from './child/index.js'
- export default class Demo5 extends React.Component{
- constructor(args){
- super(args)
- this.state = {
- 'mockNum': 0
- }
- }
- handleClick(){
- this.setState({
- 'mockNum': 0,
- })
- }
- render(){
- console.log('父级 state ==============>', this.state)
- return(
- <div>
- <button onClick={this.handleClick.bind(this)}> 点击 </button>
- <MockChild mockNum={this.state.mockNum}/>
- </div>
- )
- }
- }
- // 子组件
- render(){
- console.log('子级 Props =============>', this.props)
- return(
- <div></div>
- )
- }
我们反复点击按钮, 虽然 state 的值并没有任何变化, 但是我们看打印的结果!
render 重复渲染了!
可能会疑问, 我在项目并不会做这么一种无用功的! 但实际上, 当一个组件逻辑复杂起来之后, 会产生各种各样的情况. 比如:
比如一个组件的中有个包含 onChange 事件的 input, 当我改变该输入框内容的时候调用了 setState, 渲染视图的时候也会触发子组件的重新 render. 但我们明明没有做任何和子组件有联系的操作
例如上面的例子:
- // 父组件. JS
- state = {
- 'mockValue': '123'
- }
- handleChange(e){
- this.setState({
- 'value': '123',
- })
- }
- //render
- <input onChange={this.handleChange.bind(this)} />
- <MockChild />
- /*
- * 子组件不做变化
- */
很不爽, 真的! 必须给安排掉.
二, 基础的 React 优化
2.1 生命周期: shouldComponentUpdate
可能没听过 shouldComponentUpdate, 我们简单介绍一下它的执行周期
不熟悉 React 生命周期的可以看看这篇文章:《React 生命周期执行顺序详解》
可以看到它是在 render 渲染之前触发的, 只要我们在这里加以判断就可以有效阻止'无用'的 render 触发. 当然你说 componentWillReceiveProps 也可以, 当然! 但是它只有 props 的更新判断, 而有时候我们也不能放过未更改的 state!
- shouldComponentUpdate(nextProps, nextState){
- if(nextState !== this.state && nextProps !== this.props) return true;
- return false
- }
这么简短的代码就可以解决冗余的 render 触发. 当然有时候项目规模大了之后, 就需要具体到某一个值
- nextState.xxx !== this.state.xxx && ...
- 2.2 PureComponent
先介绍 PureComponent 的用法, 实在是太简便了
- import React, {
- PureComponent
- } from 'react'
- export default class Demo5 extends PureComponent{
- }
实际上就是把 React.Component 代替成 PureComponent.
可能会疑问那 PureComponent 应该是一个插件吧? 为什么就在 react 包里? 其实它是官方在 React15.3 提出的一个'纯净的组件'
在版本允许的情况下, 还是建议使用 PureComponent, 既能优化 render 次数, 也能减少 shouldComponentUpdate 的代码. 但是 PureComponent 只是执行了浅比较
什么是浅比较呢?
我们先来看看源码
- if (this._compositeType === CompositeTypes.PureClass) {
- shouldUpdate = !shallowEqual(prevProps, nextProps)
- || !shallowEqual(inst.state, nextState);
- }
可以看到判断主要是通过 shallowEqual 方法执行的 (即可以判断 state, 也可以判断 props)
shallowEqual 具体作用是什么呢? 实际上它使用了 ES6 的 Object.keys. 只是做了以下这么几个判断:
新的和旧的 props(state) 是否两者都有相同的 key?
引用是否改变
第二种可能有点难以理解, 什么是引用是否改变?
简单地解释就是, 是否有新的元素参与改变
举个官方常用例子:
- class App extends PureComponent {
- state = {
- items: [1, 2, 3]
- }
- handleClick = () => {
- const { items } = this.state;
- items.pop();
- this.setState({ items });
- }
- render() {
- return (<div>
- <ul>
- {this.state.items.map(i => <li key={i}>{i}</li>)}
- </ul>
- <button onClick={this.handleClick}>delete</button>
- </div>)
- }
- }
可以看到, 我们点击 delete 的时候, 虽然 items 数组执行了 pop() 方法, 删除最后一项! 但是 li 标签却没变少! 那是因为 shallowEqual 根本不吃你这套. 它认为 items 还是同一个引用, 所以给我 true! 在通过! 反过来就是 false 了
那要如果改变引用呢?
我们可以这样尝试.(这也是解决浅比较常用的一个办法)
- this.setState({
- items: [].concat(items)
- });
当然, 大多数情况我们都可以通过 PureComponent 解决啦! 实在不行, 还可以通过 componentWillReceiveProps 进一步判断呢!
PureComponent 更多的介绍可以看:《React PureComponent 使用指南》
来源: https://www.cnblogs.com/soyxiaobi/p/9794292.html