Hello,大家好,今天的分享由团队的 uncle13 老师提供。
虽然 React 本身利用 virtual DOM
和 diff
算法实现了高效的性能,但在实际项目中,还有不少的方法或技巧,可以进一步提升 React 项目的性能。
今天我们就将分享几种能有效提升 React 性能的方法。
PS:React 中的组件分为 Function 组件和 Class 组件,优化的手段根据其特性不同也分为两类,由于篇幅有限,而且 Class 组件已逐渐退出历史舞台,我们今天主要针对 Function
组件的优化进行介绍。
大家都知道,React
在组件触发刷新的时候,会深度遍历所有子组件,查找所有更新的节点。
也就是说,如果父组件刷新,子组件必然会跟着刷新。
假如这次的刷新,和我们子组件没有关系,该怎么减少这种波及呢?
这就可以用到 React.memo
了。
React.memo
是 React 提供的一个高阶组件,用于优化组件的性能。它可以在某些情况下避免不必要的组件重新渲染,从而提高应用程序的性能。
其使用方式分为两种:
React.memo
来指定自定义的比较函数。这个比较函数接收两个参数,分别是前一次的 props
和当前的 props
,返回一个布尔值表示是否需要重新渲染组件import React from 'react';
const areEqual = (prevProps, nextProps) => {
// 自定义比较逻辑
// 返回 true 表示两个 props 相等,不需要重新渲染
// 返回 false 表示两个 props 不相等,需要重新渲染
return prevProps.value === nextProps.value;
};
const MyComponent = React.memo((props) => {
console.log('Rendering MyComponent');
return <div>{props.value}</div>;
}, areEqual);
如果是在 Class 组件中,则一般使用 shouldComponentUpdate
或PureComponent
实现类似的优化。
key
属性是一个特殊的属性,它是出现不是给开发者用的,而是给react自己用的。比如你给一个子组件设置 key 之后,并不能通过子组件的 props
获取到 key
属性。
更具体地来说,组件的key
属性是为了提高 diff
算法在渲染列表时候的性能。有了它,react
内部就知道相比上一个渲染周期,当前的渲染周期插入,移动或者删除哪些节点。然后,我们就通过复用相应的组件实例来复用之前的 DOM 对象,减少不必要的 DOM 操作所产生的开销,从而提高界面更新的性能。
在项目开发中,key属性的使用场景最多的还是由数组动态创建的子组件的情况,需要为每个子组件添加唯一的 key 属性值。
举个简单的例子,我们可以给 Todo List 中的每个待办事项元素,提供唯一的 key。
const TodoList = ({ todos }) => {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
};
注意事项:为了正确使用key属性,确保所提供的key是稳定、唯一且可预测的。
不推荐使用索引作为key,因为索引可能会随着列表的增删而改变,导致性能问题和渲染错误。更好的做法是使用具有稳定唯一性的唯一标识符作为key,例如数据库分配的ID或其他全局唯一标识符。
在介绍本节内容前,先问大家一个问题:useCallback
和 useMemo
有什么关系?
可能很多人会说,useCallback
返回一个记忆化的回调函数,这个函数只有在依赖项改变时才会发生变化。而useMemo
是用来缓存计算结果的,返回的是数值。
其实这种理解有些片面,因为 useMemo
的返回值可以是函数,我们可以把 useCallback
当成是 useMemo
缓存函数时的一种语法糖,使用 useCallback
可以减少些额外的嵌套函数。
// 在 React 内部的简化实现
function useCallback(fn, dependencies) {
return useMemo(() => fn, dependencies);
}
介绍 useMemo
和 useCallback
的文章有很多,我们今天只做下简单的总结:
比如下面的代码,我们使用 React.memo 对子组件进行缓存,同时使用 useMemo
和 useCallback
对传递给子组件的属性值进行缓存。
const OtherCompMemo = React.memo(OtherComp)
const App = ({val}) => {
const [count, setCount] = useState(0)
const onClick = useCallback(() => {
// ...
}, [])
const data = useMemo(() => val, [val])
return (
<>
<button onClick={() => setCount(count+1)}>点我</button>
<OtherCompMemo onClick={onClick} data={data} />
</>
)
}
通过使用React.lazy()
和 Suspense
,在需要时才加载组件,从而减少初始加载时间并提高性能。
实际项目中的应用程序,其中包含多个页面,并且每个页面都有自己的组件。我们可以使用懒加载和代码分割来延迟加载这些页面组件,以减少初始加载时间并提高性能。
首先,我们使用 React.lazy()
函数对页面组件进行懒加载和代码分割。
例如,我们有一个名为HomePage的页面组件:
const HomePage = () => {
// 页面内容
};
现在,我们将使用React.lazy()将其包装起来,以便在需要时进行延迟加载:
const HomePage = React.lazy(() => import('./HomePage'));
接下来,在应用程序的路由配置中使用Suspense
组件,以在加载懒加载组件时显示加载指示器或其他备选内容:
import { BrowserRouter as Router, Route, Switch, Suspense } from 'react-router-dom';
const App = () => {
return (
<Router>
<Switch>
<Suspense fallback={<div>Loading...</div>}>
<Route exact path="/" component={HomePage} />
{/* 其他路由配置 */}
</Suspense>
</Switch>
</Router>
);
};
在上述示例中,当用户访问首页时,才会触发HomePage组件的加载。在加载期间,Suspense
组件将显示"Loading..."文本作为加载指示器。一旦HomePage
组件加载完成,它将被渲染到页面上。
通过使用懒加载和代码分割,我们可以将页面组件的加载延迟到实际需要时,而不是在初始加载时一次性加载所有组件。这减少了初始加载时间,并且只有当用户访问相应的页面时才会加载相关组件,提高了性能和用户体验。
不过 React.lazy()
目前仅支持默认导出(default exports
)。如果要懒加载具名导出(named exports),可以结合使用React.lazy()
和import()
函数:
const HomePage = React.lazy(() => import('./HomePage').then(module => ({ default: module.HomePage })));
虚拟列表,是很多优化系列文章中提到的一种方式。原理其实也很简单,对长列表或大型数据集,使用虚拟化库(如react-virtualized)可以仅渲染可见部分,而不是全部内容,实现性能的提升。
比如在社交媒体应用程序中,其中有一个帖子列表页面,用户可以浏览和滚动大量的帖子。为了提高性能,并避免在渲染大量帖子时产生性能问题,我们可以使用虚拟化库(如react-virtualized)来仅渲染可见部分内容。
首先,我们需要安装并导入 react-virtualized
库:
import { List, AutoSizer } from 'react-virtualized';
接下来,我们准备帖子数据,并创建一个帖子列表组件PostList,使用List组件来进行虚拟化渲染:
const PostList = ({ posts }) => {
const rowRenderer = ({ index, key, style }) => {
const post = posts[index];
return (
<div key={key} style={style}>
<PostItem post={post} />
</div>
);
};
return (
<AutoSizer>
{({ height, width }) => (
<List
height={height}
width={width}
rowCount={posts.length}
rowHeight={100}
rowRenderer={rowRenderer}
/>
)}
</AutoSizer>
);
};
通过指定height和width属性,我们告诉List组件列表的可视区域大小。rowCount属性表示帖子的总数量,而rowHeight属性表示每个帖子项的高度。
当用户滚动页面时,库会根据需要动态加载和卸载帖子项,从而提供平滑的滚动体验,并避免性能问题。
<Profiler>
测量性能 <Profiler>
用于编程式测量 React 树的渲染性能。
用法也很简单:
<Profiler id="App" onRender={onRender}>
<App />
</Profiler>
当调用 onRender 回调函数时,React 会告诉你相关信息。
function onRender(id, phase, actualDuration, baseDuration, startTime, commitTime) {
// 对渲染时间进行汇总或记录...
}
它可以帮助我们定位哪些组件渲染较慢,以及哪些组件触发了不必要的渲染。
PS:进行性能分析会增加一些额外的开销,因此在默认情况下,它在生产环境中是被禁用的。
顺便也给我们的辅导服务打个广告,现在报名支持指定导师哦~