react课程12-React中可优化的部分以及useEffect的补充

Last updated on September 9, 2024 am

本节探讨如何优化React项目,以及对useEffect的用法做一个小小的补充

一、避免不必要的渲染

方法:memo、useMemo、useCallback、将元素传递到其他元素中作为children或其他道具

组件实例只能在三种不同的情况下重新呈现:

​ 状态改变、context改变、父组件重新渲染

wasted render: 没有在DOM产生任何变化的渲染

(使程序停滞或无响应)

image-20240903184623448

选择设置激活记录渲染的原因,然后点击开始录制和结束录制就可以获取每次渲染的信息

使用TEST应用“将元素传递到其他元素中作为children或其他道具”这个方法:

​ 当一个组件中包含了一个渲染时间很长的组件,每当大组件重新渲染的时候都会重新渲染这个组件。但是把组件当作children传入的时候,就不会将它重新渲染了。

​ why?作为children,它最先被渲染,每次组件渲染时,直接将children移交,不会受到状态更新的影响。

添加工具咯:

Memoization

image-20240903191433311 image-20240903191638380

父组件中的状态改变会重新渲染所有子组件,所以当一个子组件渲染速度过慢时,会使其他组件也受到影响。(经常更新,heavy,收到的道具经常不变)

image-20240903192958326

这样包装起来就OK了。

image-20240903201449887 image-20240903201522483 image-20240903201600349

解决object:useMemo

image-20240903202608079

useCallback(记忆函数),解决function (setState函数是自动记忆的)

image-20240903203412445

减少与context相关的渲染浪费

​ 当provider是App的子组件而App重新渲染,则provider中的context的value作为对象会重新渲染。因此所有接收context的对象的组件也都会被重新渲染。(使用useMemo来创建对象)

当想要记住的组件是要被export的,就可以直接

img

二、提高应用的速度和相应能力(无延迟)

useMemo、useCallback、useTransition

三、减少Bundle的大小

方法:减少第三方包的使用,实现代码拆分和延迟加载

什么是bundle?


在前端开发中,“bundle”指的是将多个资源文件(如JavaScript、CSS、图像、字体等)打包成一个或多个文件的过程。这个打包的过程通常通过工具如Webpack、Parcel或Vite来完成。

为什么要进行打包?

  1. 减少HTTP请求:将多个文件打包成一个文件,可以减少浏览器发送的HTTP请求数量,从而加快页面的加载速度。
  2. 代码压缩和优化:打包过程中,工具可以对代码进行压缩和优化,如去除无用代码(tree-shaking)、压缩代码大小、混淆代码以提高安全性等。
  3. 模块化管理:通过打包工具,可以更好地管理项目中的模块和依赖,解决不同模块之间的依赖关系。
  4. 跨浏览器兼容性:打包工具通常还可以处理不同浏览器对JavaScript或CSS特性的支持情况,生成兼容性的代码。

image-20240904100411303

懒加载:


image-20240907144718481

懒加载(Lazy Loading)是一种优化技术,主要用于延迟加载页面中的资源或内容,直到它们真正需要被显示或使用时才进行加载。这种技术可以显著提高页面初始加载的速度,并减少不必要的资源消耗。

提升页面加载速度:通过减少初始加载的资源数量,可以加快页面的显示速度,提升用户体验。

减少带宽消耗:用户只会加载他们实际需要的资源,从而节省带宽,尤其对于移动设备用户更加友好。

提升性能:对于复杂的Web应用,懒加载可以减少内存占用和CPU消耗,因为不需要一次性加载和渲染所有内容。

const Homepage = lazy(() => import("./pages/Homepage"));

suspense


是React库中的一个组件,用于处理异步加载组件或数据时的渲染过渡。Suspense可以让您在等待异步操作完成之前显示一个备用内容(例如加载指示器),从而提升用户体验。

组件懒加载(代码分割):与React.lazy()一起使用,延迟加载组件,减少初始加载体积。

数据获取(未来特性):React团队也在扩展Suspense用于处理数据获取,但目前需要配合一些第三方库,如relayreact-query

步骤:

  1. 使用React.lazy()函数懒加载组件。
  2. 使用<Suspense>组件包裹懒加载的组件,并提供一个fallback属性来指定加载期间显示的UI。

image-20240904102540533

四、Don’t Optimize Prematurely

引用自计算机科学家Donald Knuth的名言:“Premature optimization is the root of all evil” (过早优化是万恶之源)。这条原则强调在程序开发的早期阶段,不应该花费过多时间在微小的性能优化上

  1. 浪费时间和资源

在开发初期,程序的结构和需求可能尚未完全明确。如果过早地进行优化,可能会导致大量的时间和资源浪费在处理非关键的性能问题上,而这些问题可能在后续的开发中根本不会出现。

  1. 降低代码可读性和可维护性

许多优化措施可能会使代码变得复杂难懂。这不仅增加了代码的维护难度,还可能导致后续的开发和调试变得更加困难。简洁、清晰的代码通常比复杂的、高度优化的代码更容易维护。

  1. 难以预测的性能瓶颈

在程序开发的早期,真正的性能瓶颈可能尚不明确。过早优化可能会针对一些并不是实际瓶颈的部分进行,而忽略了更为重要的性能问题。只有在程序的主要功能基本完成并经过性能测试后,才能更准确地识别和解决实际的瓶颈。

  1. 延迟项目进度

过早的优化可能会分散开发人员的注意力,使他们无法专注于实现核心功能。这样会导致项目的整体进度被延迟,影响最终的交付时间。

  1. 优化的效果有限

在没有明确需求和数据支持的情况下,进行优化往往难以显著提升性能。相反,通过后期的分析和针对性优化,往往可以在关键部分取得更大的性能提升。

五、useEffect

依赖数组:

​ ✅state variable(状态变量)、prop、context value、reactive value(反应值:与状态有关的变量或函数)

​ ❌objects、arrays(他们在每次渲染都会被重新创建,react会判定为旧的和新的不同)

image-20240906152320236

Effect最好是作为最后的选择来使用!!!以下是它被过度使用的用例(然鹅前面的项目一直在使用。。)

image-20240906152347059
stale closures:

Stale closure”是一个与闭包(closure)相关的编程问题,通常在处理异步操作或回调时容易出现。它指的是闭包内部引用了外部作用域的变量,但在闭包执行时,这些变量的值已经发生了变化,导致闭包内部使用了过时(stale)的值。

六、补充contextAPI+useReducerRedux在优化上的区别

(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-thunkRedux-saga 等中间件可以优化异步操作的处理方式,减少不必要的状态更新和数据请求。
  • 优化点:通过合理设计中间件逻辑,可以避免重复的 API 请求或复杂的状态变更,进而优化应用性能。
5. 使用 combineReducers 切分 reducer
  • Redux 支持使用 combineReducers 将状态分片管理,避免每次状态更新时整个状态树都被重新计算。
  • 优化点:将状态管理分离成多个独立的 reducer,每个 reducer 只负责更新它自己管理的状态,这样可以确保应用只会在状态相关部分发生变化时触发更新,而不会影响其他不相关的状态。
6. React-Redux 的 connectuseSelector
  • connectuseSelector 提供了基于 Redux 状态的高效选择器机制。connect 使用了 shouldComponentUpdate 来优化组件的重渲染,而 useSelector 也支持通过比较函数(equality function)来决定是否重新渲染组件。
  • 优化点:通过 connectuseSelector 的正确使用,组件只会在其关心的状态发生变化时才重新渲染,减少不必要的渲染。
7. 批处理(Batching Updates)
  • Redux 在某些情况下支持批量更新状态,从而减少多次不必要的渲染。React-Redux 自动支持 React 的批处理更新功能(batching),确保在一个事件循环中,状态变化会被批处理,从而避免多次重新渲染。
  • 优化点:批处理可以确保在一次状态变化过程中,多个组件的渲染被合并处理,减少性能开销。
总结

Redux 提供了很多优化工具,开发者可以通过使用 ReselectRedux DevToolsImmutable 数据结构、中间件、combineReducers、以及 React-Redux 提供的 connectuseSelector 等机制,轻松优化应用的性能。而 Context API + useReducer 则需要手动实现类似的优化机制,尤其是在组件重渲染控制和复杂异步逻辑处理上,Redux 的优化工具更加成熟和开箱即用。

(2)Context API + useReducer

Context API + useReducer 优化较为复杂,主要原因在于它没有像 Redux 那样内置的优化机制和工具,开发者需要手动处理状态管理和性能优化的问题。以下是具体原因:

1. Context 的全局重渲染
  • 问题:Context API 的一个主要问题是,当上下文(Context)中的状态发生变化时,所有使用该 Context 的组件都会重新渲染,即使某些组件并不依赖这个变化的数据。这可能导致不必要的重渲染,影响性能。
  • 原因:React Context 的设计是为了简化跨组件传递数据,但它没有内置类似于 Redux 的细粒度状态更新机制。任何状态变化都会影响使用该 Context 的所有组件。
  • 解决方法:开发者需要手动通过分离 Context 或使用 useMemouseCallback 来减少不必要的重渲染。这就增加了复杂性,特别是在应用变得庞大时,需要手动进行性能优化。
2. 缺乏像 Reselect 这样的记忆化选择器
  • 问题useReducer 没有像 Redux 的 Reselect 这样方便的选择器库,开发者需要手动实现选择器(selectors)来提取和处理状态中的数据,并进行记忆化优化。
  • 原因:Context API 和 useReducer 只是 React 内置的状态管理机制,它们并不包含对状态读取或选择逻辑的优化。如果需要基于状态的某些部分进行计算或处理,开发者可能需要自己编写记忆化逻辑,这相对 Redux 来说更加复杂。
  • 解决方法:开发者可以借助 React 的 useMemo 来手动实现记忆化选择器,但这仍然需要自行编写和管理。
3. 异步操作和中间件的处理复杂
  • 问题useReducer 没有内置的异步处理机制,也没有像 Redux 中的 redux-thunkredux-saga 这样的中间件,开发者需要手动管理异步操作的状态。
  • 原因useReducer 是一个同步的 reducer 函数,设计上不支持处理异步逻辑。与 Redux 中通过中间件轻松处理异步状态不同,在 useReducer 中管理异步操作需要通过多个 useEffect 结合状态处理,这会导致代码变得更加复杂。
  • 解决方法:通常,开发者需要将异步逻辑放入 useEffect 钩子中手动处理,或者使用第三方库来帮助简化异步状态管理。
4. 复杂状态拆分与管理
  • 问题:当应用的状态变得复杂时,Context API + useReducer 可能会显得笨重。对于复杂的全局状态管理,拆分和维护多个 useReducer 以及对应的 Context 会变得难以维护。
  • 原因useReducer 没有类似于 Redux 的 combineReducers 方法来自动分片管理不同的状态。开发者需要手动管理多个 ContextReducer,并处理它们之间的依赖关系,这会让应用的复杂度增加。
  • 解决方法:可以通过模块化拆分 ReducerContext,但这需要开发者自行管理每个部分的状态和更新逻辑,增加了维护成本。
5. 缺乏调试和开发工具支持
  • 问题Context API + useReducer 没有像 Redux DevTools 这样强大的调试工具,开发者调试状态更新和流转的过程更加困难。
  • 原因:Redux DevTools 提供了丰富的功能,如时间旅行、action 日志、状态快照等。而 Context API + useReducer 缺少这样的工具支持,调试状态流转时只能依赖常规的 React 开发者工具,调试效率较低。
  • 解决方法:开发者可以通过手动调试和添加日志的方式解决,但这比使用 Redux DevTools 直接查看状态变化要复杂得多。
总结

Context API + useReducer 优化较为复杂,主要是因为它缺乏自动化的性能优化工具和机制。Context 全局状态变化时会导致所有订阅组件重渲染,手动管理状态拆分和异步逻辑也相对繁琐。相比之下,Redux 提供了很多开箱即用的优化工具和中间件,可以帮助开发者更轻松地管理和优化状态流转。


react课程12-React中可优化的部分以及useEffect的补充
http://example.com/2024/09/03/react课程12-React中可优化的部分以及useEffect的补充/
Author
Yaodeer
Posted on
September 3, 2024
Licensed under