React 应用程序的 6 个优化技巧

简介

在过去的几年里,JavaScript框架已经完全改变了我们构建应用程序的方式,而Reaction也在这个转换过程中占据了相当的份额。优化页面加载时间很重要,因为页面加载所用的时间与退回和转化率直接相关。在本教程中,我们将看看大多数开发人员在使用Reaction构建应用程序时会犯的六个常见错误。我们还将讨论如何避免这些错误,并重点介绍如何将页面加载时间保持在尽可能低的水平。

Reaction工作原理

上图解释了你每次使用Reaction应用程序时发生的事情。每个Reaction应用程序都以根组件--在我们的例子中是App--开始,并由组件--AddGroceryGroceryListSearchBar组成。这些子组件由一些函数组成,这些函数根据其中包含的属性和状态将UI呈现给DOM。

用户以不同的方式与呈现的UI交互--通过填写表单或单击按钮。当这种情况发生时,事件被传递回父组件。这些事件会导致应用程序的状态发生变化,从而促使Reaction在其虚拟DOM中重新呈现UI。

对于返回的每个事件,Reaction的引擎必须将虚拟DOM与实际DOM进行比较,并计算是否有必要使用新发现的数据更新实际DOM。现在,事情变得混乱的地方是,如果我们的应用程序的结构是每次单击和滚动都会重复反应,比较和更新虚拟DOM和真实DOM之间的更改。这可能会导致应用程序极其缓慢和低效。

常见错误及避免方法

下面是开发人员在构建应用程序时犯下的三个糟糕做法。这些错误降低了效率,总体上增加了页面加载时间,请尽可能避免它们。

不必要的导入

每当您在应用程序中导入整个库时,它都会被解构,以访问您需要的模块。一两个小的库可能没有坏处,但当您导入大量的库和依赖项时,您应该只导入所需的模块:

1import assign from "101";
2import Map from "immutable";

上面的代码导入整个库,并开始对其进行解构以访问assignmap。我们不这样做,而是使用一个称为樱桃挑选的概念,它只获取应用程序所需的必要部分:

1import assign from "101/assign";
2import Map from "immutable/src/map";

在JSX中嵌入函数

众所周知,JavaScript是一种垃圾收集语言。它的垃圾回收器通过尝试回收部分应用程序不再使用的内存来管理内存。在Render中定义函数将在每次重新呈现包含组件时创建该函数的新实例。当这种做法在整个应用程序中发生时,垃圾回收器最终会以内存泄漏的形式出现问题-这种情况下,应用程序的某些部分使用的内存即使不再被这些部分使用,也不会被释放:

 1class GroceryList extends React.Component {
 2    state = {
 3        groceries: [],
 4        selectedGroceryId: null
 5    }
 6
 7    render(){
 8        const { groceries } = this.state;
 9        return (
10           groceries.map((grocery)=>{
11               return <Grocery onClick={(e)=>{
12                    this.setState({selectedGroceryId:grocery.groceryId})
13               }} grocery={grocery} key={grocery.id}/>
14           }) 
15        )
16    }
17}

正确的做法是在render之前定义一个新函数onGroceryClick

 1class GroceryList extends React.Component {
 2    state = {
 3        groceries: [],
 4        selectedGroceryId: null
 5    }
 6
 7    onGroceryClick = (groceryId)=>{
 8        this.setState({selectedGroceryId:groceryId})
 9    }
10
11    render(){
12        const { groceries } = this.state;
13        return (
14           groceries.map((grocery)=>{
15               return <Grocery onClick={this.onGroceryClick} 
16                grocery={grocery} key={grocery.id}/>
17           }) 
18        )
19    }
20}

在DOM元素中使用扩散运算符

在构建应用程序时,以无保留的方式使用spread运算符是一种糟糕的做法。这将有未知的属性飞来飞去,这只是一个时间问题,事情开始变得复杂。下面是一个简单的例子:

1const GroceriesLabel = props => {
2    return (
3      <div {...props}>
4        {props.text}
5      </div>
6    );
7  };

随着您正在构建的应用程序变得更大,。。.props可以包含任何内容。更好、更安全的做法是确定您需要的任何值:

1const GroceriesLabel = props => {
2    return (
3      <div particularValue={props.particularValue}>
4        {props.text}
5      </div>
6    );
7};

性能破解

使用Gzip压缩您的捆绑包

这非常高效,可以将文件大小减少高达65%。应用程序中的大多数文件将使用大量重复的文本和空格。GZIP通过压缩这些重复出现的字符串来处理这个问题,从而极大地缩短了网站的第一次呈现时间。GZIP使用了webpack在生产过程中用于压缩文件的本地插件compressionPlugin来对您的文件进行预压缩,首先让我们使用compressionPlugin创建一个压缩包:

1plugins: [
2    new CompressionPlugin({
3        asset: "[path].gz[query]",
4        algorithm: "gzip",
5        test: /.js$|.css$|.html$/,
6        threshold: 10240,
7        minRatio: 0.8
8    })
9]

一旦文件被压缩,就可以使用中间件提供服务:

 1//this middleware serves all js files as gzip
 2app.use(function(req, res, next) {
 3    const originalPath = req.path;
 4    if (!originalPath.endsWith(".js")) {
 5        next();
 6        return;
 7    }
 8    try {
 9        const stats = fs.statSync(path.join("public", `${req.path}.gz`));
10        res.append('Content-Encoding', 'gzip');
11        res.setHeader('Vary', 'Accept-Encoding');
12        res.setHeader('Cache-Control', 'public, max-age=512000');
13        req.url = `${req.url}.gz`;
14
15        const type = mime.lookup(path.join("public", originalPath));
16        if (typeof type != 'undefined') {
17            const charset = mime.charsets.lookup(type);
18            res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
19        }
20    } catch (e) {}
21    next();
22})

回忆化Reaction组件

记忆的概念并不新鲜-它是一种存储昂贵的函数调用并在相同的输入再次出现时返回缓存结果的技术。Reaction中的记忆通过记忆数据计算来工作,从而使状态变化尽可能快地发生。要了解它的工作原理,请看下面的Reaction组件:

 1const GroceryDetails = ({grocery, onEdit}) => {
 2    const {name, price, grocery_img} = grocery;
 3    return (
 4        <div className="grocery-detail-wrapper">
 5            <img src={grocery_img} />
 6            <h3>{name}</h3>
 7            <p>{price}</p>
 8        </div>
 9    )
10}

在上面的代码示例中,GroceryDetails中的所有子对象都是基于道具的。更改道具会导致GroceryDetails重新渲染。如果GroceryDetail是一个不太可能更改的组件,那么应该对其进行备注。早期版本的Reaction(<V16.6.0)的用户将使用moize,这是一个用于JavaScript的记忆库。看看下面的语法:

 1import moize from 'moize/flow-typed';
 2
 3const GroceryDetails = ({grocery, onEdit}) =>{
 4    const {name, price, grocery_img} = grocery;
 5
 6    return (
 7        <div className="grocery-detail-wrapper">
 8            <img src={grocery_img} />
 9            <h3>{name}</h3>
10            <p>{price}</p>
11        </div>
12    )
13}
14
15export default moize(GroceryDetails,{
16    isReact: true
17});

在上面的代码块中,moize将所有传递的道具和上下文存储在GroceryDetails中,并使用它们来检测组件是否有更新。只要道具和上下文保持不变,就会返回缓存值。这确保了超级快速的渲染。

对于使用更高版本的React(高于V16.6.0)的用户,您可以使用React.memo而不是moize:

 1const GroceryDetails = ({grocery, onEdit}) =>{
 2    const {name, price, grocery_img} = grocery;
 3
 4    return (
 5        <div className="grocery-detail-wrapper">
 6            <img src={grocery_img} />
 7            <h3>{name}</h3>
 8            <p>{price}</p>
 9        </div>
10    )
11}
12
13export default React.memo(GroceryDetails);

考虑使用服务器端渲染

服务器端呈现(SSR)是前端框架在后端系统上运行时呈现标记的能力。

您应该在单页应用程序中利用SSR。您的应用程序的用户可以在发送的初始请求返回响应后立即收到一个完全呈现的HTML页面,而不是让用户等待您的JavaScript文件加载。通常,服务器端呈现的应用程序使用户能够比客户端呈现的应用程序更快地接收内容。React中用于服务器端呈现的一些解决方案包括Next.js和Gatsby。

结论

对于那些可能不熟悉这些概念的普通初学者,我建议你看看这门课程开始使用React,因为它涵盖了大量内容-从处理状态到创建组件。在React应用程序中优化页面加载时间的重要性不可低估。您的应用程序不仅可以促进更好的SEO并获得更高的转化率,而且通过遵循最佳实践,您将能够更容易地检测错误。 这些概念可能需要很多努力,但它们值得一试。Memoize React Components by Tony Quetano和Selva Ganesh的How to serve a Webpack gzip file in production帮助撰写了这篇文章。

Published At
Categories with 技术
Tagged with
comments powered by Disqus