React从入门到精通系列之(16)性能优化

十六、性能优化

在React内部,React使用了几种比较聪明的技术来实现最小化更新UI所需的昂贵的DOM操作的数量。

对于许多应用来说,使用React将很快速的渲染出用户界面,从而无需进行大量工作来专门做优化性能的工作。

大概有以下有几种方法来加快你的React应用程序。

使用生产环境的配置进行构建

如果你在React应用中进行基准测试或这遇到了性能问题,请首先确保你是使用的压缩后线上版本js文件来进行的测试:

  • 对于Create React App来说,你需要在构建时运行npm run build

  • 对于单文件来说,我们提供了生产环境版本.min.js

  • 使用的是Browserify,你先设置NODE_ENV=production然后运行。

  • 使用的是webpack,你需要在生产环境配置中加入以下插件:

new webpack.DefinePlugin({
    'process.env': {
        NODE_ENV: JSON.stringify('production')
    }
}),new webpack.optimize.UglifyJSPlugin();

在构建应用程序时开发构建工具可以打印一些有帮助的额外警告。
但是由于需要额外地记录这些警告信息,所以它也会变得更慢。

避免重复处理DOM

React会创建并维护所渲染的UI内部表示信息。其中包括从组件返回的React元素。 此表示信息使React避免创建DOM节点和访问那些没有必要的节点,因为这样做可能会比JavaScript对象上的一些操作更慢。 有时它被称为“虚拟DOM”

当组件的propsstate更改时,React通过将最新返回的元素与先前渲染的元素进行比较来决定是否需要实际的DOM更新。 当它们不相等时,React将更新DOM。

在某些情况下,您的组件可以通过重写生命周期函数shouldComponentUpdate来加快所有这些操作。这个函数会在重新渲染之前触发。 此函数的默认实现返回true,让React执行更新:

shouldComponentUpdate(nextProps,nextState) {
    return true;
}

如果你知道在某些情况下你的组件不需要更新,你可以从shouldComponentUpdate中返回false,而不是跳过整个渲染过程,其中包括调用当前组件和下面的render()

shouldComponentUpdate的应用

这里是一个组件的子树。 对于其中每一个子树来说,SCU指示shouldComponentUpdate返回什么,vDOMEq指示渲染的React元素是否相等。 最后,圆圈的颜色表示组件是否必须重新处理。

因为shouldComponentUpdate对于以C2为根的子树返回了false,所以React没有尝试渲染C2,因此甚至不必在C4C5上调用shouldComponentUpdate

对于C1C3shouldComponentUpdate返回true,因此React必须下到子树中并检查它们。 对于C6 子树shouldComponentUpdate返回true,并且因为渲染的元素不是相同的,React不得不更新DOM。

最后一个有趣的例子是C8。 React不得不渲染这个组件,不过由于React元素返回的元素等于之前渲染的元素,所以它不必更新DOM。

注意,React只需要做C6的DOM重新处理,这是不可避免的。
对于C8,它通过比较渲染的React元素来决定是否重新处理DOM。至于C2的子树和C7,我们在shouldComponentUpdate返回false时它甚至都不需要比较元素,并且也没有调用render()

例子

如果你的组件的唯一的改变方式就是改变props.colorstate.count,你可以用shouldComponentUpdate检查:

import React from 'react';
import ReactDOM from 'react-dom';

class CounterButton extends React.Component {
    constructor(props) {
        super(props);
        this.state = {count: 1};
        this.click = this.click.bind(this);
    }

    click() {
        this.setState(prevState => ({
            count: prevState.count + 1
        }));
    }

    shouldComponentUpdate(nextProps,nextState) {
        if (this.props.color !== nextProps.color) {
            return true;
        }
        return this.state.count !== nextState.count;
    }

    render() {
        return (
            <button color={this.props.color} onClick={this.click}>
                Count:{this.state.count}
            </button>
        );
    }
}
ReactDOM.render(
    <CounterButton color="blue"/>,document.getElementById('root')
);

在这段代码中,shouldComponentUpdate只是检查props.colorstate.count是否有任何变化。 如果它们的值没有更改,则组件不更新。 如果你的组件比这个例子中的组件更复杂,你可以使用类似的模式在props和state的所有字段之间做一个“浅比较”,以确定组件是否应该更新。

比较常见的模式是使用React提供的一个帮助对象来使用这个逻辑,可以直接继承React.PureComponent
所以上面这段代码有一个更简单的方法来实现同样的事情:

import React from 'react';
import ReactDOM from 'react-dom';

class CounterButton extends React.PureComponent {
    constructor(props) {
        super(props);
        this.state = {count: 1};
        this.click = this.click.bind(this);
    }

    click() {
        this.setState(prevState => ({
            count: prevState.count + 1
        }));
    }

    render() {
        return (
            <button color={this.props.color} onClick={this.click}>
                Count: {this.state.count}
            </button>
        );
    }
}
ReactDOM.render(
    <CounterButton color="blue"/>,document.getElementById('root')
);

大多数时候,你可以使用React.PureComponent而不是编写自己的shouldComponentUpdate。 它只做一个浅层的比较,所以你不需要直接使用它,如果你的组件内部propsstate的数据有可能会突然变化,那么浅比较将失效。

浅比较的失效可能是一个更加复杂的数据结构问题(突然变化)。 例如,假设您想要一个以逗号分隔单词列表的ListOfWords组件,使用一个父WordAdder组件,当你单击一个按钮用来添加一个单词到列表中时。 下面的代码将无法正常工作:

// PureComponent在内部会帮我们对props和state进行简单对比(浅比较)
// 值类型比较值,引用类型比较引用,但是不会比较引用类型的内部数据是否改变。
// 所以就会出现一个bug,不管你怎么点button,div是不会增加的。
class ListOfWords extends React.PureComponent {
    render() {
        return <div>{this.props.words.join(',')}</div>;
    }
}

class WordAdder extends React.Component {
    constructor(props) {
        super(props);
        this.state = {words: ['zhangyatao']};
        this.click = this.click.bind(this);
    }
    click() {
        // 这么写是不对的,因为state的更新是异步的,所以可能会导致一些不必要的bug
        const words = this.state.word;
        words.push('zhangyatao');
        this.setState({words: words});
    }
    render() {
        return (
            <div>
                <button onClick={this.click} />
                <ListOfWords words={this.state.words} />
            </div>
        );
    }
}

问题是PureComponent将对this.props.words的旧值和新值进行简单比较。 由于这个代码在WordAdderclick方法中改变了单词数组,所以即使数组中的实际单词已经改变,ListOfWords组件中的this.props.words的旧值和新值还是相等的。 因此即便ListOfWords具有要被渲染出来的新单词它也还是不更新任何内容。

超能力之『不会突然变化的数据』

避免此问题的最简单的方法就是避免将那些可能突然变化的数据作为你的props或state。 例如,上面的click方法里面使用concat代替push

click() {
    this.setState(prevState => ({
        count: prevState.words.concat(['zhangyatao'])
    }));
}

ES6支持数组的spread语法可以让这变得更容易。 如果您使用的是Create React App,那么此语法默认可以使用的。

click() {
    this.setState(prevState => ({
        words: [...prevState.words,'zhangyatao']
    }));
}

您还可以把那部分有可能突然变化的数据的代码按照上面的方式给重写下,从而以避免这种问题。
例如,假设我们有一个名为colormap的对象,我们要写一个函数,将colormap.right改为'blue'。 我们可以写:

function updateColorMap(colormap) {
    colormap.right = 'blue';
}

要将上面的代码写成不会濡染改变的对象,我们可以使用Object.assign方法:

function updateColorMap(colormap) {
    return Object.assign(colormap,{right: 'blue'});
}

updateColorMap现在会返回一个新对象,而不是改变之前的旧对象。 Object.assign在ES6中,需要polyfill

有一个JavaScript提议来添加对象spread属性,以便不会突然变化的更新对象:

function updateColorMap(colormap) {
    return {...colormap,right: 'blue'};
}

如果使用Create React App,默认情况下Object.assign和对象spread语法都可用。

使用不突变的数据结构

Immutable.js是另一种解决这个问题的方法。 它提供不可变的,持久的集合,通过结构共享工作:

  • 不可变:一旦创建,集合不能在另一个时间点更改。

  • 持久性:可以从先前的集合和类集合的突变中创建处一个新集合。 创建新集合后,原始集合仍然有效。

  • 结构共享:使用尽可能多的与原始集合相同的结构创建新集合,从而将最低程度的减少复制来提高性能。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom