React应用程序的6个优化技巧
- Introduction 在过去的几年里,JavaScript 框架彻底改变了我们构建应用程序的方式,React 在转换过程中占有相当的份额。 优化页面加载时间很重要,因为页面加载时间与跳出率和转化率直接相关。 在本教程中,我们将了解大多数开发人员在使用 React 构建应用程序时常犯的六个错误。 我们还将讨论如何避免这些错误,并强调一些有用的技巧,以尽可能缩短页面加载时间。
反应如何工作
上图解释了每次使用 React 应用程序时会发生什么。 每个 React 应用程序都从一个根组件开始——在我们的例子中是 App
——并且由“子”组件组成——AddGrocery
、GroceryList
和 [X170X ]。 这些子组件包含根据其中包含的属性和状态将 UI 呈现给 DOM 的函数。
用户以不同的方式与呈现的 UI 交互——通过填写表单或单击按钮。 发生这种情况时,事件将被转发回父组件。 这些事件会导致应用程序状态发生变化,从而促使 React 在其虚拟 DOM 中重新渲染 UI。
对于每个被发送回来的事件,React 的引擎必须将虚拟 DOM 与真实 DOM 进行比较,并计算是否有必要使用这些新发现的数据更新真实 DOM。 现在,如果我们的应用程序的结构方式是每次点击和滚动都会导致 React 反复比较和更新虚拟 DOM 和真实 DOM 之间的变化,事情就会变得混乱。 这可能导致应用程序非常缓慢和低效。
常见错误以及如何避免它们
以下是开发人员在构建应用程序时的三种不良做法。 这些错误会降低效率并增加整体页面加载时间,尽可能避免它们。
不必要的进口
每当您在应用程序中导入整个库时,它都会被解构以访问您需要的模块。 一两个小库可能没有害处,但是当您导入大量库和依赖项时,您应该只导入您需要的模块:
import assign from "101"; import Map from "immutable";
上面的代码导入整个库并开始对其进行解构以访问 assign
和 Map
。 我们不这样做,而是使用一个称为“樱桃采摘”的概念,它只抓取应用程序所需的必要部分:
import assign from "101/assign"; import Map from "immutable/src/map";
在 JSX 中嵌入函数
JavaScript 是众所周知的垃圾收集语言。 它的垃圾收集器通过尝试回收应用程序部分不再使用的内存来管理内存。 每次重新渲染包含的组件时,在 render 中定义一个函数将创建一个新的函数实例。 当这种做法在你的应用程序中发生时,它最终会以内存泄漏的形式成为垃圾收集器的问题——在这种情况下,应用程序的某些部分使用的内存没有被释放,即使这些部分不再使用它:
class GroceryList extends React.Component { state = { groceries: [], selectedGroceryId: null } render(){ const { groceries } = this.state; return ( groceries.map((grocery)=>{ return <Grocery onClick={(e)=>{ this.setState({selectedGroceryId:grocery.groceryId}) }} grocery={grocery} key={grocery.id}/> }) ) } }
正确的做法是在 render
之前定义一个新函数 onGroceryClick
:
class GroceryList extends React.Component { state = { groceries: [], selectedGroceryId: null } onGroceryClick = (groceryId)=>{ this.setState({selectedGroceryId:groceryId}) } render(){ const { groceries } = this.state; return ( groceries.map((grocery)=>{ return <Grocery onClick={this.onGroceryClick} grocery={grocery} key={grocery.id}/> }) ) } }
在 DOM 元素中使用扩展运算符
在构建应用程序时,以无保留的方式使用扩展运算符是一种不好的做法。 这将具有未知的属性,事情开始变得复杂只是时间问题。 这是一个简短的例子:
const GroceriesLabel = props => { return ( <div {...props}> {props.text} </div> ); };
随着您正在构建的应用程序变得越来越大,. . .props
可以包含任何内容。 更好、更安全的做法是确定您需要的任何值:
const GroceriesLabel = props => { return ( <div particularValue={props.particularValue}> {props.text} </div> ); };
性能黑客
使用 Gzip 压缩包
这非常有效,可以将文件大小减少多达 65%。 应用程序中的大多数文件将使用大量重复的文本和空格。 Gzip 通过压缩这些经常出现的字符串来处理这个问题,从而大大缩短了您网站的首次渲染时间。 Gzip 使用 compressionPlugin
预压缩你的文件——Webpack 用于在生产期间压缩文件的本地插件,首先让我们使用 compressionPlugin
创建一个压缩包:
plugins: [ new CompressionPlugin({ asset: "[path].gz[query]", algorithm: "gzip", test: /.js$|.css$|.html$/, threshold: 10240, minRatio: 0.8 }) ]
一旦文件被压缩,就可以使用中间件来提供它:
//this middleware serves all js files as gzip app.use(function(req, res, next) { const originalPath = req.path; if (!originalPath.endsWith(".js")) { next(); return; } try { const stats = fs.statSync(path.join("public", `${req.path}.gz`)); res.append('Content-Encoding', 'gzip'); res.setHeader('Vary', 'Accept-Encoding'); res.setHeader('Cache-Control', 'public, max-age=512000'); req.url = `${req.url}.gz`; const type = mime.lookup(path.join("public", originalPath)); if (typeof type != 'undefined') { const charset = mime.charsets.lookup(type); res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')); } } catch (e) {} next(); })
记忆 React 组件
memoization 的概念并不新鲜——它是一种存储昂贵的函数调用并在再次出现相同输入时返回缓存结果的技术。 React 中的记忆化通过记忆数据计算来工作,以便状态的变化尽快发生。 要了解它是如何工作的,请查看下面的 React 组件:
const GroceryDetails = ({grocery, onEdit}) => { const {name, price, grocery_img} = grocery; return ( <div className="grocery-detail-wrapper"> <img src={grocery_img} /> <h3>{name}</h3> <p>{price}</p> </div> ) }
在上面的代码示例中,GroceryDetails
中的所有孩子都是基于道具的。 更改道具将导致 GroceryDetails
重新渲染。 如果 GroceryDetails 是一个不太可能更改的组件,则应该对其进行记忆。 React 早期版本(< V16.6.0)的用户会使用moize
,一个用于 JavaScript 的记忆库。 看看下面的语法:
import moize from 'moize/flow-typed'; const GroceryDetails = ({grocery, onEdit}) =>{ const {name, price, grocery_img} = grocery; return ( <div className="grocery-detail-wrapper"> <img src={grocery_img} /> <h3>{name}</h3> <p>{price}</p> </div> ) } export default moize(GroceryDetails,{ isReact: true });
在上面的代码块中,moize
将任何传递的道具和上下文存储在 GroceryDetails
中,并使用它们来检测组件是否有更新。 只要 props 和 context 保持不变,就会返回缓存的值。 这确保了超快速渲染。
对于使用 React 更高版本(高于 V16.6.0)的用户,您可以使用 React.memo
代替 moize:
const GroceryDetails = ({grocery, onEdit}) =>{ const {name, price, grocery_img} = grocery; return ( <div className="grocery-detail-wrapper"> <img src={grocery_img} /> <h3>{name}</h3> <p>{price}</p> </div> ) } export default React.memo(GroceryDetails);
考虑使用服务器端渲染
服务器端渲染 (SSR) 是前端框架在后端系统上运行时渲染标记的能力。
您应该将 SSR 与单页应用程序一起使用。 您的应用程序的用户无需等待您的 JavaScript 文件加载,而是在发送的初始请求返回响应后立即收到完全呈现的 HTML 页面。 通常,服务器端呈现的应用程序使用户能够比客户端呈现的应用程序更快地接收内容。 React 中服务端渲染的一些解决方案包括 Next.js 和 Gatsby。
结论
对于可能不熟悉这些概念的普通初学者,我建议您查看 开始使用 React 的这门课程,因为它涵盖了很多内容——从处理状态到创建组件。 在 React 应用程序中优化页面加载时间的重要性不可低估。 您的应用程序不仅可以促进更好的 SEO 并获得更高的转化率,通过遵循最佳实践,您将能够更轻松地检测错误。 这些概念可能需要付出很多努力,但值得一试。 Tony Quetano 的 Memoize React Components 和 Selva Ganesh 的 How to serve a Webpack gzipped file in production 等帖子帮助编写了这篇文章。