东城做网站,网址seo查询,淄博网站制作设计公司,网站备案注销找哪个部门在React项目中是经常会使用到useMemo#xff0c;useCallBack的#xff0c;这是两个优化性能的方法#xff0c;那么useMemo#xff0c;useCallBack到底是什么呢#xff1f;什么时候用呢#xff1f; 下面将给打击分享相关知识#xff0c;希望对大家有所帮助同时欢迎讨论指…
在React项目中是经常会使用到useMemouseCallBack的这是两个优化性能的方法那么useMemouseCallBack到底是什么呢什么时候用呢 下面将给打击分享相关知识希望对大家有所帮助同时欢迎讨论指出问题
1.useMemo
useMemo会在在组件首次加载和重渲染期间执行执行的函数需要和渲染相关的。
使用示例如下
import React, {useMemo, useState} from react;
function Memo (){ const [count, setCount] useState(0) const [bool, setBool] useState(true)
useMemo(() { console.log(useMemo) }, [count])
return ( div div{count}/div div{bool ? 正面 : 反面}/div button onClick{() setCount(count 1)}1哟/button button onClick{() setBool(!bool)}点击取反操作/button /div ) } export default Memo; useMemo 类似于Vue的计算属性computed监听某个值的变化根据变化的值重新计算新值通过点击触发改变count的值组件会重新渲染且useMemo会监听count值的变化进行重新计算但是当我触发改变bool的值组件只进行了重新渲染但是useMemo没有重新计算并执行因为当前useMomo监听的时count的变化其他变化则不会去管 2.useCallback
useCallback会在渲染期间执行返回一个函数useCallback是用来帮忙缓存函数的当依赖项没有发生变化时返回缓存的指针当在依赖项变化的时候会更新返回一个新的函数
使用示例
import React, { useState, useCallback } from react; export default function App() { const [count1, setCount1] useState(0); const [count2, setCount2] useState(0); const handleClickButton1 () {setCount1(count1 1)}; const handleClickButton2 useCallback(() { console.log(useCallback) setCount2(count2 1); }, [count2]); return ( div div Button onClick{handleClickButton1}点击Button1/Button /div div Button onClick{handleClickButton2}点击Button2/Button /div /div ); }
详细讲解经过useCallback优化后当Button2 是点击触发时自身时才会更新Button1只要父组件更新后才会变更。
为什么使用 useMemo 和 useCallback
使用 memo 通常有三个原因
✅ 防止不必要的 effect。❗️防止不必要的 re-render。❗️防止不必要的重复计算。
后两种优化往往被误用导致出现大量的无效优化或冗余优化。下面详细介绍这三个优化方式。
防止不必要的 effect
如果一个值被 useEffect 依赖那它可能需要被缓存这样可以避免重复执行 effect。
const Component () { // 在 re-renders 之间缓存 a 的引用 const a useMemo(() ({ test: 1 }), []); useEffect(() { // 只有当 a 的值变化时这里才会被触发 doSomething(); }, [a]); // the rest of the code };
useCallback 同理
const Component () { // 在 re-renders 之间缓存 fetch 函数 const fetch useCallback(() { console.log(fetch some data here); }, []); useEffect(() { // 仅fetch函数的值被改变时这里才会被触发 fetch(); }, [fetch]); // the rest of the code };
当变量直接或者通过依赖链成为 useEffect 的依赖项时那它可能需要被缓存。这是 useMemo 和 useCallback 最基本的用法。
防止不必要的 re-render
进入重点环节了。正确的阻止 re-render 需要我们明确三个问题
组件什么时候会 re-render。如何防止子组件 re-render。如何判断子组件需要缓存。
1. 组件什么时候会 re-render
三种情况
当本身的 props 或 state 改变时。Context value 改变时使用该值的组件会 re-render。当父组件重新渲染时它所有的子组件都会 re-render形成一条 re-render 链。
第三个 re-render 时机经常被开发者忽视导致代码中存在大量的无效缓存。
例如
const App () { const [state, setState] useState(1); const onClick useCallback(() { console.log(Do something on click); }, []); return ( // 无论 onClick 是否被缓存Page 都会 re-render Page onClick{onClick} / ); };
当使用 setState 改变 state 时App 会 re-render作为子组件的 Page 也会跟着 re-render。这里 useCallback 是完全无效的它并不能阻止 Page 的 re-render。
2. 如何防止子组件 re-render
必须同时缓存 onClick 和组件本身才能实现 Page 不触发 re-render。
const PageMemoized React.memo(Page); const App () { const [state, setState] useState(1); const onClick useCallback(() { console.log(Do something on click); }, []); return ( // Page 和 onClick 同时 memorize PageMemoized onClick{onClick} / ); };
由于使用了React.memoPageMemoized 会浅比较 props 的变化后再决定是否 re-render。onClick 被缓存后不会再变化所以 PageMemoized 不再 re-render。
然而如果 PageMemoized 再添加一个未被缓存的 props一切就前功尽弃
const PageMemoized React.memo(Page); const App () { const [state, setState] useState(1); const onClick useCallback(() { console.log(Do something on click); }, []); return ( // page WILL re-render because value is not memoized PageMemoized onClick{onClick} value{[1, 2, 3]} / ); };
由于 value 会随着 App 的 re-render 重新定义引用值发生变化导致 PageMemoized 仍然会触发 re-render。
现在可以得出结论了必须同时满足以下两个条件子组件才不会 re-render
子组件自身被缓存。子组件所有的 prop 都被缓存。
3. 如何判断子组件需要缓存
我们已经了解为了防止子组件 re-render需要以下成本
开发者工作量的增加 一旦使用缓存就必须保证组件本身以及所有 props 都缓存后续添加的所有 props 都要缓存。代码复杂度和可读性的变化代码中出现大量缓存函数这会增加代码复杂度并降低易读性。
除此之外还有另外一个成本性能成本。 组件的缓存是在初始化时进行虽然每个组件缓存的性能耗费很低通常不足1ms但大型程序里成百上千的组件如果同时初始化缓存成本可能会变得很可观。
所以局部使用 memo比全局使用显的更优雅、性能更好坏处是需要开发者主动去判断是否需要缓存该子组件。 那应该什么时候缓存组件怎么判断一个组件的渲染是昂贵的
很遗憾似乎没有一个简单无侵入自动的衡量方式。通常来说有两个方式
人肉判断开发或者测试人员在研发过程中感知到渲染性能问题并进行判断。通过工具目前有一些工具协助开发者在查看组件性能:如 React Dev Tools Profiler这篇文章介绍了使用方式如这个 hooksuseRenderTimes另外React 在 16.5版本后提供了 Profiler API_它可以识别出应用中渲染较慢的部分或是可以使用类似 memoization 优化的部分_。所以可以通过 puppeteer 或 cypress 在自动化集成中测试组件性能这很适合核心组件的性能测试。
防止不必要的重复计算
如 React 文档所说useMemo 的基本作用是避免在每次渲染时都进行高开销的计算。 那什么是“高开销的计算”
高开销的计算其实极少出现如下示例对包含 250 个 item 的数组 countries 进行排序、渲染并计算耗时。
const List ({ countries }) { const before performance.now(); const sortedCountries orderBy(countries, name, sort); // this is the number were after const after performance.now() - before; return ( // same ) };
结果如图所示排序耗时仅用了 4 毫秒而渲染图中的 List 组件仅仅只是 button 文字却用了 20 毫秒5倍的差距代码详见 codesandbox.。 大部分情况下我们的计算量要比这个 250 个 item 的数组少而组件渲染要比这个 List 组件复杂的多所以真实程序中计算和渲染的性能差距会更大。
可见组件渲染才是性能的瓶颈应该把 useMemo 用在程序里渲染昂贵的组件上而不是数值计算上。当然除非这个计算真的很昂贵比如阶乘计算。
至于为什么不给所有的组件都使用 useMemo上文已经解释了。useMemo 是有成本的它会增加整体程序初始化的耗时并不适合全局全面使用它更适合做局部的优化。
为什么 React 没有把缓存组件作为默认配置
关于这点 Dan Abramov 在推文上也给出了解释虽然是个类比 评论区里 react 的另一位核心开发者 Christopher Chedeau 也参与了讨论。 简而言之他们认为
缓存是有成本的小的成本可能会累加过高。默认缓存无法保证足够的正确性。
原因 2 的原文correctness is not guaranteed for everything because people can mutate things. Christopher Chedeau 未给出进一步解释。或许他是指可能会导致跟 PureComponent相同的问题即浅比较 mutate things 时由于浅比较相等导致组件未能 update 的问题。
结论
讲到这里我们可以总结出 useMemo/useCallback 使用准则了
大部分的 useMemo 和 useCallback 都应该移除他们可能没有带来任何性能上的优化反而增加了程序首次渲染的负担并增加程序的复杂性。使用 useMemo 和 useCallback 优化子组件 re-render 时必须同时满足以下条件才有效。 子组件已通过 React.memo 或 useMemo 被缓存子组件所有的 prop 都被缓存不推荐默认给所有组件都使用缓存大量组件初始化时被缓存可能导致过多的内存消耗并影响程序初始化渲染的速度。
关于第三点有相反观点详见Why We Memo All the Things作者推荐默认给全部组件都加上 React.memo并给所有 props 都套上 useMemo。他认为这样可以降低工程师心智负担让工程师不必再自己判断什么时候使用 memorize。
总结
相同点
useCallback 和 useMemo 都是性能优化的手段类似于类组件中的 shouldComponentUpdate在子组件中使用 shouldComponentUpdate 判定该组件的 props 和 state 是否有变化从而避免每次父组件render时都去重新渲染子组件。 不同点
useCallback 和 useMemo 的区别是useCallback返回一个函数当把它返回的这个函数作为子组件使用时可以避免每次父组件更新时都重新渲染这个子组件。