十六、性能优化

本文介绍如何利用React内置的性能优化技术提升应用效率,包括使用生产环境配置构建、避免不必要的DOM操作,以及通过shouldComponentUpdate控制组件更新逻辑。还讨论了如何处理可能会导致组件状态突然变化的数据,以确保性能优化的一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

十六、性能优化

在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是另一种解决这个问题的方法。 它提供不可变的,持久的集合,通过结构共享工作:

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

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

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

基于数据挖掘的音乐推荐系统设计与实现 需要一个代码说明,不需要论文 采用python语言,django框架,mysql数据库开发 编程环境:pycharm,mysql8.0 系统分为前台+后台模式开发 网站前台: 用户注册, 登录 搜索音乐,音乐欣赏(可以在线进行播放) 用户登陆时选择相关感兴趣的音乐风格 音乐收藏 音乐推荐算法:(重点) 本课题需要大量用户行为(如播放记录、收藏列表)、音乐特征(如音频特征、歌曲元数据)等数据 (1)根据用户之间相似性或关联性,给一个用户推荐与其相似或有关联的其他用户所感兴趣的音乐; (2)根据音乐之间的相似性或关联性,给一个用户推荐与其感兴趣的音乐相似或有关联的其他音乐。 基于用户的推荐和基于物品的推荐 其中基于用户的推荐是基于用户的相似度找出相似相似用户,然后向目标用户推荐其相似用户喜欢的东西(和你类似的人也喜欢**东西); 而基于物品的推荐是基于物品的相似度找出相似的物品做推荐(喜欢该音乐的人还喜欢了**音乐); 管理员 管理员信息管理 注册用户管理,审核 音乐爬虫(爬虫方式爬取网站音乐数据) 音乐信息管理(上传歌曲MP3,以便前台播放) 音乐收藏管理 用户 用户资料修改 我的音乐收藏 完整前后端源码,部署后可正常运行! 环境说明 开发语言:python后端 python版本:3.7 数据库:mysql 5.7+ 数据库工具:Navicat11+ 开发软件:pycharm
MPU6050是一款广泛应用在无人机、机器人和运动设备中的六轴姿态传感器,它集成了三轴陀螺仪和三轴加速度计。这款传感器能够实时监测并提供设备的角速度和线性加速度数据,对于理解物体的动态运动状态至关重要。在Arduino平台上,通过特定的库文件可以方便地与MPU6050进行通信,获取并解析传感器数据。 `MPU6050.cpp`和`MPU6050.h`是Arduino库的关键组成部分。`MPU6050.h`是头文件,包含了定义传感器接口和函数声明。它定义了类`MPU6050`,该类包含了初始化传感器、读取数据等方法。例如,`begin()`函数用于设置传感器的工作模式和I2C地址,`getAcceleration()`和`getGyroscope()`则分别用于获取加速度和角速度数据。 在Arduino项目中,首先需要包含`MPU6050.h`头文件,然后创建`MPU6050`对象,并调用`begin()`函数初始化传感器。之后,可以通过循环调用`getAcceleration()`和`getGyroscope()`来不断更新传感器读数。为了处理这些原始数据,通常还需要进行校准和滤波,以消除噪声和漂移。 I2C通信协议是MPU6050与Arduino交互的基础,它是一种低引脚数的串行通信协议,允许多个设备共享一对数据线。Arduino板上的Wire库提供了I2C通信的底层支持,使得用户无需深入了解通信细节,就能方便地与MPU6050交互。 MPU6050传感器的数据包括加速度(X、Y、Z轴)和角速度(同样为X、Y、Z轴)。加速度数据可以用来计算物体的静态位置和动态运动,而角速度数据则能反映物体转动的速度。结合这两个数据,可以进一步计算出物体的姿态(如角度和角速度变化)。 在嵌入式开发领域,特别是使用STM32微控制器时,也可以找到类似的库来驱动MPU6050。STM32通常具有更强大的处理能力和更多的GPIO口,可以实现更复杂的控制算法。然而,基本的传感器操作流程和数据处理原理与Arduino平台相似。 在实际应用中,除了基本的传感器读取,还可能涉及到温度补偿、低功耗模式设置、DMP(数字运动处理器)功能的利用等高级特性。DMP可以帮助处理传感器数据,实现更高级的运动估计,减轻主控制器的计算负担。 MPU6050是一个强大的六轴传感器,广泛应用于各种需要实时运动追踪的项目中。通过 Arduino 或 STM32 的库文件,开发者可以轻松地与传感器交互,获取并处理数据,实现各种创新应用。博客和其他开源资源是学习和解决问题的重要途径,通过这些资源,开发者可以获得关于MPU6050的详细信息和实践指南
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值