react课程12-React中可优化的部分以及useEffect的补充
Last updated on September 9, 2024 am
本节探讨如何优化React项目,以及对useEffect的用法做一个小小的补充
一、避免不必要的渲染
方法:memo、useMemo、useCallback、将元素传递到其他元素中作为children或其他道具
组件实例只能在三种不同的情况下重新呈现:
状态改变、context改变、父组件重新渲染
wasted render: 没有在DOM产生任何变化的渲染
(使程序停滞或无响应)
选择设置激活记录渲染的原因,然后点击开始录制和结束录制就可以获取每次渲染的信息
使用TEST应用“将元素传递到其他元素中作为children或其他道具”这个方法:
当一个组件中包含了一个渲染时间很长的组件,每当大组件重新渲染的时候都会重新渲染这个组件。但是把组件当作children传入的时候,就不会将它重新渲染了。
why?作为children,它最先被渲染,每次组件渲染时,直接将children移交,不会受到状态更新的影响。
添加工具咯:
Memoization
父组件中的状态改变会重新渲染所有子组件,所以当一个子组件渲染速度过慢时,会使其他组件也受到影响。(经常更新,heavy,收到的道具经常不变)
这样包装起来就OK了。
解决object:useMemo
useCallback(记忆函数),解决function (setState函数是自动记忆的)
减少与context相关的渲染浪费
当provider是App的子组件而App重新渲染,则provider中的context的value作为对象会重新渲染。因此所有接收context的对象的组件也都会被重新渲染。(使用useMemo来创建对象)
当想要记住的组件是要被export的,就可以直接
二、提高应用的速度和相应能力(无延迟)
useMemo、useCallback、useTransition
三、减少Bundle的大小
方法:减少第三方包的使用,实现代码拆分和延迟加载
什么是bundle?
在前端开发中,“bundle”指的是将多个资源文件(如JavaScript、CSS、图像、字体等)打包成一个或多个文件的过程。这个打包的过程通常通过工具如Webpack、Parcel或Vite来完成。
为什么要进行打包?
- 减少HTTP请求:将多个文件打包成一个文件,可以减少浏览器发送的HTTP请求数量,从而加快页面的加载速度。
- 代码压缩和优化:打包过程中,工具可以对代码进行压缩和优化,如去除无用代码(tree-shaking)、压缩代码大小、混淆代码以提高安全性等。
- 模块化管理:通过打包工具,可以更好地管理项目中的模块和依赖,解决不同模块之间的依赖关系。
- 跨浏览器兼容性:打包工具通常还可以处理不同浏览器对JavaScript或CSS特性的支持情况,生成兼容性的代码。
懒加载:
懒加载(Lazy Loading)是一种优化技术,主要用于延迟加载页面中的资源或内容,直到它们真正需要被显示或使用时才进行加载。这种技术可以显著提高页面初始加载的速度,并减少不必要的资源消耗。
提升页面加载速度:通过减少初始加载的资源数量,可以加快页面的显示速度,提升用户体验。
减少带宽消耗:用户只会加载他们实际需要的资源,从而节省带宽,尤其对于移动设备用户更加友好。
提升性能:对于复杂的Web应用,懒加载可以减少内存占用和CPU消耗,因为不需要一次性加载和渲染所有内容。
const Homepage = lazy(() => import("./pages/Homepage"));
suspense
是React库中的一个组件,用于处理异步加载组件或数据时的渲染过渡。Suspense
可以让您在等待异步操作完成之前显示一个备用内容(例如加载指示器),从而提升用户体验。
组件懒加载(代码分割):与React.lazy()
一起使用,延迟加载组件,减少初始加载体积。
数据获取(未来特性):React团队也在扩展Suspense
用于处理数据获取,但目前需要配合一些第三方库,如relay
或react-query
。
步骤:
- 使用
React.lazy()
函数懒加载组件。 - 使用
<Suspense>
组件包裹懒加载的组件,并提供一个fallback
属性来指定加载期间显示的UI。
四、Don’t Optimize Prematurely
引用自计算机科学家Donald Knuth的名言:“Premature optimization is the root of all evil” (过早优化是万恶之源)。这条原则强调在程序开发的早期阶段,不应该花费过多时间在微小的性能优化上
- 浪费时间和资源
在开发初期,程序的结构和需求可能尚未完全明确。如果过早地进行优化,可能会导致大量的时间和资源浪费在处理非关键的性能问题上,而这些问题可能在后续的开发中根本不会出现。
- 降低代码可读性和可维护性
许多优化措施可能会使代码变得复杂难懂。这不仅增加了代码的维护难度,还可能导致后续的开发和调试变得更加困难。简洁、清晰的代码通常比复杂的、高度优化的代码更容易维护。
- 难以预测的性能瓶颈
在程序开发的早期,真正的性能瓶颈可能尚不明确。过早优化可能会针对一些并不是实际瓶颈的部分进行,而忽略了更为重要的性能问题。只有在程序的主要功能基本完成并经过性能测试后,才能更准确地识别和解决实际的瓶颈。
- 延迟项目进度
过早的优化可能会分散开发人员的注意力,使他们无法专注于实现核心功能。这样会导致项目的整体进度被延迟,影响最终的交付时间。
- 优化的效果有限
在没有明确需求和数据支持的情况下,进行优化往往难以显著提升性能。相反,通过后期的分析和针对性优化,往往可以在关键部分取得更大的性能提升。
五、useEffect
依赖数组:
✅state variable(状态变量)、prop、context value、reactive value(反应值:与状态有关的变量或函数)
❌objects、arrays(他们在每次渲染都会被重新创建,react会判定为旧的和新的不同)
Effect最好是作为最后的选择来使用!!!以下是它被过度使用的用例(然鹅前面的项目一直在使用。。)
stale closures:
“Stale closure”是一个与闭包(closure)相关的编程问题,通常在处理异步操作或回调时容易出现。它指的是闭包内部引用了外部作用域的变量,但在闭包执行时,这些变量的值已经发生了变化,导致闭包内部使用了过时(stale)的值。
六、补充contextAPI+useReducer
和Redux
在优化上的区别
(1)contextAPI+useReducer
在状态管理优化方面,Redux 提供了许多开箱即用的工具和功能,使得优化性能相对简单。以下是 Redux 提供的一些常见优化手段:
1. Redux DevTools
- Redux DevTools 提供了一种强大的调试工具,可以跟踪状态变化、时间旅行调试、回滚状态等。它不仅有助于调试,还可以帮助开发者分析和优化状态更新。
- 性能优化:通过 Redux DevTools,可以实时查看哪些 action 触发了状态更新,分析哪些部分的状态管理和渲染需要优化。
2. Selector 和 Reselect
- Reselect 是 Redux 官方推荐的一个选择器库,用于创建带记忆功能的选择器(memoized selectors)。它通过缓存计算结果来避免不必要的计算和重渲染。
- 优化点:当状态更新时,如果未实际使用的数据未发生变化,Reselect 能确保组件不会重新计算或渲染,提升性能。
3. Immutable 数据管理
- Redux 强调使用不可变数据,这样可以轻松地检测数据是否变化,从而优化状态更新过程。不可变性保证了当状态更新时,只有发生了实际变化的数据才会触发重新渲染。
- 优化点:通过严格遵循不可变数据的原则,Redux 可以快速地检查状态是否发生变化,并仅更新相关组件,减少了不必要的渲染。
4. 中间件优化
- Redux-thunk 和 Redux-saga 等中间件可以优化异步操作的处理方式,减少不必要的状态更新和数据请求。
- 优化点:通过合理设计中间件逻辑,可以避免重复的 API 请求或复杂的状态变更,进而优化应用性能。
5. 使用 combineReducers 切分 reducer
- Redux 支持使用
combineReducers
将状态分片管理,避免每次状态更新时整个状态树都被重新计算。 - 优化点:将状态管理分离成多个独立的 reducer,每个 reducer 只负责更新它自己管理的状态,这样可以确保应用只会在状态相关部分发生变化时触发更新,而不会影响其他不相关的状态。
6. React-Redux 的 connect
和 useSelector
- connect 和 useSelector 提供了基于 Redux 状态的高效选择器机制。
connect
使用了shouldComponentUpdate
来优化组件的重渲染,而useSelector
也支持通过比较函数(equality function)来决定是否重新渲染组件。 - 优化点:通过
connect
和useSelector
的正确使用,组件只会在其关心的状态发生变化时才重新渲染,减少不必要的渲染。
7. 批处理(Batching Updates)
- Redux 在某些情况下支持批量更新状态,从而减少多次不必要的渲染。React-Redux 自动支持 React 的批处理更新功能(batching),确保在一个事件循环中,状态变化会被批处理,从而避免多次重新渲染。
- 优化点:批处理可以确保在一次状态变化过程中,多个组件的渲染被合并处理,减少性能开销。
总结
Redux 提供了很多优化工具,开发者可以通过使用 Reselect
、Redux DevTools
、Immutable 数据结构
、中间件、combineReducers
、以及 React-Redux
提供的 connect
和 useSelector
等机制,轻松优化应用的性能。而 Context API + useReducer
则需要手动实现类似的优化机制,尤其是在组件重渲染控制和复杂异步逻辑处理上,Redux 的优化工具更加成熟和开箱即用。
(2)Context API + useReducer
Context API + useReducer
优化较为复杂,主要原因在于它没有像 Redux 那样内置的优化机制和工具,开发者需要手动处理状态管理和性能优化的问题。以下是具体原因:
1. Context 的全局重渲染
- 问题:Context API 的一个主要问题是,当上下文(Context)中的状态发生变化时,所有使用该 Context 的组件都会重新渲染,即使某些组件并不依赖这个变化的数据。这可能导致不必要的重渲染,影响性能。
- 原因:React Context 的设计是为了简化跨组件传递数据,但它没有内置类似于 Redux 的细粒度状态更新机制。任何状态变化都会影响使用该 Context 的所有组件。
- 解决方法:开发者需要手动通过分离 Context 或使用
useMemo
、useCallback
来减少不必要的重渲染。这就增加了复杂性,特别是在应用变得庞大时,需要手动进行性能优化。
2. 缺乏像 Reselect 这样的记忆化选择器
- 问题:
useReducer
没有像 Redux 的 Reselect 这样方便的选择器库,开发者需要手动实现选择器(selectors)来提取和处理状态中的数据,并进行记忆化优化。 - 原因:Context API 和
useReducer
只是 React 内置的状态管理机制,它们并不包含对状态读取或选择逻辑的优化。如果需要基于状态的某些部分进行计算或处理,开发者可能需要自己编写记忆化逻辑,这相对 Redux 来说更加复杂。 - 解决方法:开发者可以借助 React 的
useMemo
来手动实现记忆化选择器,但这仍然需要自行编写和管理。
3. 异步操作和中间件的处理复杂
- 问题:
useReducer
没有内置的异步处理机制,也没有像 Redux 中的redux-thunk
或redux-saga
这样的中间件,开发者需要手动管理异步操作的状态。 - 原因:
useReducer
是一个同步的 reducer 函数,设计上不支持处理异步逻辑。与 Redux 中通过中间件轻松处理异步状态不同,在useReducer
中管理异步操作需要通过多个useEffect
结合状态处理,这会导致代码变得更加复杂。 - 解决方法:通常,开发者需要将异步逻辑放入
useEffect
钩子中手动处理,或者使用第三方库来帮助简化异步状态管理。
4. 复杂状态拆分与管理
- 问题:当应用的状态变得复杂时,Context API +
useReducer
可能会显得笨重。对于复杂的全局状态管理,拆分和维护多个useReducer
以及对应的Context
会变得难以维护。 - 原因:
useReducer
没有类似于 Redux 的combineReducers
方法来自动分片管理不同的状态。开发者需要手动管理多个Context
和Reducer
,并处理它们之间的依赖关系,这会让应用的复杂度增加。 - 解决方法:可以通过模块化拆分
Reducer
和Context
,但这需要开发者自行管理每个部分的状态和更新逻辑,增加了维护成本。
5. 缺乏调试和开发工具支持
- 问题:
Context API + useReducer
没有像 Redux DevTools 这样强大的调试工具,开发者调试状态更新和流转的过程更加困难。 - 原因:Redux DevTools 提供了丰富的功能,如时间旅行、action 日志、状态快照等。而
Context API + useReducer
缺少这样的工具支持,调试状态流转时只能依赖常规的 React 开发者工具,调试效率较低。 - 解决方法:开发者可以通过手动调试和添加日志的方式解决,但这比使用 Redux DevTools 直接查看状态变化要复杂得多。
总结
Context API + useReducer
优化较为复杂,主要是因为它缺乏自动化的性能优化工具和机制。Context 全局状态变化时会导致所有订阅组件重渲染,手动管理状态拆分和异步逻辑也相对繁琐。相比之下,Redux 提供了很多开箱即用的优化工具和中间件,可以帮助开发者更轻松地管理和优化状态流转。