如何使用 Ref 解决 React 性能问题
以及我们是如何阻止 React Context 重新渲染所有内容的。
Refs 是 React 中一个很少使用的功能。如果你读过 官方的 React 指南 ,就会发现它被介绍为一种跳出典型 React 数据流的“逃生舱”,并警告要谨慎使用,而且它主要被宣传为访问组件底层 DOM 元素的正确方法。
但除了 Hooks 的概念之外,React 团队还引入了 useRefHook,它扩展了这一功能:
useRef()它的用途不仅限于 ref属性本身。它还可以 方便地保存任何可变值, 类似于在类中使用实例字段的方式。
虽然我在新的 Hook API 发布时忽略了这一点,但事实证明它非常有用。
👉 点击此处直接查看解决方案和代码片段
问题
我是一名软件工程师,正在开发 Rowy ,这是一个开源的 React 应用,它将电子表格的用户界面与 Firestore 和 Firebase 的强大功能相结合。它的一个关键特性是 侧边抽屉 ,这是一个类似表单的界面,用于编辑单行数据,可以滑到主表格上方。
当用户点击表格中的某个单元格时,可以打开侧边栏来编辑该单元格对应的行。换句话说,侧边栏中显示的内容取决于当前选中的行——这个信息应该存储在状态中。
将此状态放置在侧边抽屉组件本身是最合乎逻辑的位置,因为当用户选择不同的单元格时,它应该 只 影响侧边抽屉。然而:
React 的建议是 将此状态提升 到组件最近的共同祖先,在本例中是 `<component>` TablePage。但我们决定不在此处移动状态,因为:
TablePage它不包含任何状态,主要用作桌子和侧抽屉组件的容器,这两个组件都没有接收任何属性。我们更倾向于保持这种状态。
我们已经通过位于组件树根部附近的 上下文 共享了大量“全局”数据,因此我们觉得将此状态添加到该中央数据存储中是有意义的。
附注:即使我们将状态放入其中 TablePage,我们仍然会遇到下面同样的问题。
问题在于,每当用户选择一个单元格或打开侧边栏时,全局上下文的更新都会导致 整个应用程序重新渲染 。这包括主表格组件,该组件可能同时显示数十个单元格,每个单元格都有自己的编辑器组件。这将导致大约 650 毫秒 的渲染时间(!),足以让侧边栏的打开动画出现明显的延迟。
请注意点击打开按钮到侧边抽屉打开动画之间的延迟。
其背后的原因是上下文的一个关键特性——这也是为什么在 React 中使用上下文比使用全局 JavaScript 变量更好的原因:
当 Provider 的 valueprop 发生变化时,所有作为 Provider 后代的消费者都会重新渲染。
虽然到目前为止,这种对 React 状态和生命周期的 Hook 一直对我们很有帮助,但现在看来,我们似乎搬起石头砸了自己的脚。
顿悟时刻
我们首先探索了几种不同的解决方案(来自 Dan Abramov 关于此问题的帖子),最终确定了以下方案 useRef:
拆分上下文,即创建一个新的上下文 SideDrawerContext。 表格仍然需要使用新的上下文,而新的上下文在侧边抽屉打开时仍然会更新, 导致表格不必要地重新渲染 。
将表格组件包裹在 `<table>` React.memo或 `<div>`中 useMemo。 表格仍然需要调用 `<div>` useContext来访问侧边抽屉的状态,而 这两个 API 都无法阻止它导致重新渲染 。
react-data-grid对用于渲染表格的组件进行 记忆化处理 。 这会增加代码的冗余度。我们还发现,这样做会阻止 必要的 重新渲染,导致我们不得不花费更多时间修复或完全重构代码,仅仅是为了实现侧边栏抽屉功能。
在反复阅读 Hook API 文档后 useMemo,我终于注意到了这一点 useRef:
useRef()它的用途不仅限于 ref属性本身。它还可以 方便地保存任何可变值, 类似于在类中使用实例字段的方式。
更重要的是:
useRef 当其内容发生变化时,不会 通知您。修改该 .current属性 不会导致重新渲染 。
就在那时,我突然意识到:
我们不需要存储侧边抽屉的状态——我们只需要一个指向设置该状态的函数的引用。
解决方案
将开放状态和单元格状态保存在旁边的抽屉里。
创建对这些状态的引用并将其存储在上下文中。
当用户点击单元格时,使用表格中的引用调用设置状态函数(在侧边抽屉内)。
以下代码是 Rowy 上使用的代码的简化版本,其中包含 ref 的 TypeScript 类型:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
import { SideDrawerRef } from 'SideDrawer'
export function ProjectContextProvider ( { children } ) {
const sideDrawerRef = useRef < SideDrawerRef > ( ) ;
return (
< ProjectContext . Provider value = { { sideDrawerRef } } >
{ children }
</ ProjectContext . Provider >
)
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
export type SideDrawerRef = {
cell : SelectedCell ;
setCell : React . Dispatch < React . SetStateAction < SelectedCell > > ;
open : boolean ;
setOpen : React . Dispatch < React . SetStateAction < boolean > > ;
} ;
export default function SideDrawer ( ) {
const classes = useStyles ( ) ;
const { tableState, dataGridRef, sideDrawerRef } = useProjectContext ( ) ;
const [ cell , setCell ] = useState < SelectedCell > ( null ) ;
const [ open , setOpen ] = useState ( false ) ;
sideDrawerRef . current = { cell, setCell, open, setOpen } ;
...
}
附注:由于函数组件在重新渲染时会运行整个函数体,因此每当状态更新(并导致重新渲染)时 cell, open都会 sideDrawerRef始终包含最新的值 .current。
事实证明,这个方案是最佳的,因为:
当前单元格和打开状态存储在侧抽屉组件本身中,这是最合乎逻辑的放置位置。
表格组件在需要 时 可以访问其同级组件的状态。
当当前单元格或打开状态更新时,只会触发侧边抽屉组件的重新渲染,而不会触发应用程序中的任何其他组件的重新渲染。
你可以在这里 和 这里 看到 Rowy 是如何使用它的 。
何时使用Ref
但这并不意味着你应该在所有构建中都使用这种模式。它最适合用于需要 在特定时间访问或更新另一个组件的状态,但你的组件并不依赖于该状态或基于该状态进行渲染的情况 。React 的核心概念——状态提升和单向数据流——已经足以涵盖大多数应用程序架构。
感谢阅读!您可以在下方了解更多关于 Rowy 的 信息,也可以在 Twitter 上关注我 @nots_dney 。
低代码后端平台。在类似电子表格的用户界面上管理数据库,并在浏览器中使用 JS/TS 构建云函数工作流。
✨ 类似 Airtable 的数据库管理界面 ✨ 无需编写代码,即可构建任何自动化流程 ✨
连接数据库,无需离开浏览器即可使用低代码创建云函数。 专注于构建您的应用程序。适用于 Firebase 和 Google Cloud 的低代码
在线演示🛝
💥 在实时演示环境 中体验 Rowy 💥
特色✨
20211004-RowyWebsite.mp4
功能强大的 Firestore 电子表格界面
Firestore 的 CMS
CRUD 操作
批量导入或导出数据 - CSV、JSON、TSV
按行值排序和筛选
锁定、冻结、调整大小、隐藏和重命名列
同一系列的多种视图
利用云函数和现成的扩展程序实现自动化
基于字段级数据变更构建云函数工作流
使用预置代码块连接到您最喜欢的工具,或者创建您自己的代码块。
SendGrid、Algolia、Twilio、BigQuery 等
丰富且灵活的数据字段
文章来源:https://dev.to/notsidney/how-to-useref-to-fix-react-performance-issues-e8p