实用笔记:如何在不重写代码的情况下优化缓慢的 React 应用
《实用笔记》操作指南:我如何在不重写代码的情况下优化慢速的 React 应用——专为采用该模式的团队设计的契约、校验机制及即插即用代码模块。
可将此文档视为《如何在不重写代码的情况下优化缓慢的 React 应用》中内容的面向操作员的重构版本:清晰的阶段划分、有序的代码模块,以及便于交接时参考的恢复说明。在扩大范围之前,最好将“概览”阶段视为一个可量化的基准——记录一份最佳实践案例、一个故障场景以及回滚说明。应将此阶段视为输入与经过验证的输出之间的契约:为相关成果命名、明确成功标准,绝不允许出现无声的半完成状态。
问题诊断:定位故障根源
在为诊断结果确定阶段之前,应先明确输入参数、该步骤的负责人以及结束标准,然后再修改代码。操作人员应当能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。应在功能结果旁记录执行时间以及令牌或查询成本。提前了解这些成本信息,可避免在从演示环境切换到共享环境时出现意外费用。状态应与负责处理状态变更的组件放在一起;若将所有数据都存放在全局存储中,就会使得时间相关的问题更难被发现。
1. React.memo:最简单的解决方案
对于1个React memo阶段,在修改代码之前需明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审核。 状态应与负责数据变更的组件放在一起。将所有数据都放到全局存储中会使得时序错误更难被发现。
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
// Component logic
});
2. useMemo与useCallback:避免递归重计算
在处理两个 useMemo 和 useCallback 阶段时,应在修改代码之前明确输入参数、该步骤的负责人以及退出标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和异常恢复路径。重试机制、人工审核环节以及错误处理都是产品功能的一部分,而非后续需要补充的内容。 应将状态与负责数据变更的组件放在一起。如果将所有数据都放到全局存储中,就会使得时序相关的问题更难被发现。 在处理两个 useMemo 和 useCallback 阶段时,应在修改代码之前明确输入参数、该步骤的负责人以及退出标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 应将这一阶段视为输入与经过验证的输出之间的契约。为相关产物命名,明确成功判定标准,杜绝无声的半完成状态。
const computedStats = useMemo(() => {
return expensiveAggregation(data);
}, [data]);
const handleUpdate = useCallback((id) => {
updateItem(id);
}, []);
3. useTransition:优先考虑用户交互
在处理“使用transition优先考虑用户交互”这一阶段时,首先明确相关规范:所需的输入参数、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 在功能结果旁记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在代码从演示环境转向共享环境时出现意外费用。 应将各种效果视为与外部世界的同步机制,而非渲染过程中衍生值的替代品。
const [isPending, startTransition] = useTransition();
const handleSearch = (term) => {
startTransition(() => {
setSearchTerm(term);
// Filtering happens in the background
});
};
4. useDeferredValue:平滑处理高成本渲染
在处理“使用DeferredValue平滑处理”这一阶段时,首先需明确相关约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 应将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审计。 应将效果处理视为与外部世界的同步机制,而非渲染过程中替代衍生值的手段。
const deferredData = useDeferredValue(data);
// Chart receives deferredData and updates only when React has idle time
5. 虚拟化:仅渲染可见内容
在处理“仅虚拟化渲染”这一阶段时,首先需明确相关约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 同时记录正常流程与异常恢复路径。重试机制、人工审核环节以及错误处理都属于产品功能的一部分,而非后续的优化工作。 应将各种效果视为与外部世界的同步机制,而非渲染过程中衍生值的替代品。 在处理“仅虚拟化渲染”这一阶段时,首先需明确相关约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 要把这一阶段视为输入参数与经过验证的输出结果之间的契约。为相关产物命名,定义成功判定标准,绝不允许出现无声无息的部分完成情况。
import { FixedSizeList } from 'react-window';
const Row = ({ index, style }) => (
<div style={style}>
{data[index].name}
</div>
);
<FixedSizeList
height={400}
itemCount={data.length}
itemSize={35}
width="100%"
>
{Row}
</FixedSizeList>
6. 懒加载:按路由拆分代码
在将第6阶段的懒加载代码视为可度量的对象时,其效果最佳。在扩大范围之前,需记录一个成功的用例、一个失败案例以及回滚说明。 在功能结果之外还需记录执行时间以及令牌或查询的开销。提前了解成本情况,可避免在代码从演示环境转移到共享环境时出现意外费用。 尽量保持渲染工作的成本较低,只有在经过测量后,才将高成本的计算操作放在记忆化机制之后处理。过早使用记忆化可能会掩盖过时的属性相关错误。
const Dashboard = lazy(() => import('./views/Dashboard'));
const Analytics = lazy(() => import('./views/Analytics'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</Suspense>
);
}
7. useMemoizedSelector:状态管理优化
将 7 个 useMemoizedSelector 状态管理阶段视为可度量的对象来使用效果最佳。在扩大范围之前,先记录一份理想的运行示例、一个故障案例以及回滚说明。 将配置放在应用程序代码之外。环境文件、密钥存储和功能开关应集中存放,这样操作人员无需查看整个系统结构即可进行审计。 保持渲染操作的成本较低,只有在经过测量后,才将耗时的计算任务交由记忆化处理。过早使用记忆化可能会掩盖过时属性引发的错误。
import { createSelector } from '@reduxjs/toolkit';
const selectFilteredData = createSelector(
(state) => state.items,
(state) => state.filterTerm,
(items, term) => items.filter(item =>
item.name.toLowerCase().includes(term.toLowerCase())
)
);
// In component:
const filteredData = useSelector(selectFilteredData);
数据对比:实施前后
“The Numbers Before and”阶段若被视为可度量的对象,效果最佳。在扩大范围之前,需记录一份理想运行案例、一个失败案例以及回滚说明。 同时记录正常流程与恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的一部分,而非后续需要补充的内容。 尽量降低渲染成本,只有在经过评估后才将耗时的计算操作放入缓存中。过早使用缓存可能会掩盖过时属性带来的错误。 “The Numbers Before and”阶段若被视为可度量的对象,效果最佳。在扩大范围之前,需记录一份理想运行案例、一个失败案例以及回滚说明。 应将此阶段视为输入与经过验证的输出之间的契约。为相关成果命名,明确成功标准,绝不允许出现无声的半完成状态。
思维方式的转变
在“思维模式转变”阶段,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新执行该步骤,而无需猜测隐藏状态。除了功能结果外,还需记录执行时间以及令牌或查询成本。提前了解这些成本可以避免在从演示环境过渡到共享环境时出现意外费用。应将状态与负责数据变更的组件放在一起处理;若将所有数据都存放在全局存储中,就很难发现与时间相关的错误。
总结
在“核心要点”阶段,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储和功能开关应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审计。 状态信息应与负责数据变更的组件放在一起。将所有数据都存放在全局存储中会使得时序错误更难被发现。
操作检查清单
在完成操作检查清单阶段时,首先需明确相关约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改符合预期。
应优先选择小型、可测试的单元,而非庞大的脚本。当某个步骤出错时,错误应指向单一责任点,而非复杂的流程链。
应将效果视为与外部世界的同步机制,而非渲染过程中替代衍生值的手段。
锁定依赖版本,并记录用于运行演示的图像摘要。可重复性比团队内部经验更为重要。
将此阶段视为输入与经过验证的输出之间的契约。为输出结果命名,明确成功标准,拒绝默许的不完整完成状态。
应将效果视为与外部世界的同步机制,而非渲染过程中替代衍生值的手段。
在推广该技术栈之前,应先冻结版本,为关键路径生成标准记录,并明确回滚步骤。共享环境需要设置速率限制、租户验证机制,以及负责密钥轮换的明确责任人。与其展示花哨的一次性演示,不如注重扎实的可靠性。
d6a7f4e83dda的批注:不要将提供商密钥放入代码仓库,为每个会话设置令牌使用上限,并将记录存储在评估用示例文件旁,以便后续模型更换时保持数据可比性。