当前位置:首页 > 科技  > 软件

useCallback 使用的四个阶段,你都知道吗?

来源: 责编: 时间:2024-01-18 09:40:32 172观看
导读非 React 使用者估计看了都要摇头啊。一个破回调函数的运用,居然能折腾出来这么多事。一大堆文章都在探讨如何使用它更合理。事实上确实如此,在 React 独特的单向数据流刷新机制下,对于 useCallback 认知的逐渐深入实际

wi728资讯网——每日最新资讯28at.com

非 React 使用者估计看了都要摇头啊。一个破回调函数的运用,居然能折腾出来这么多事。一大堆文章都在探讨如何使用它更合理。事实上确实如此,在 React 独特的单向数据流刷新机制下,对于 useCallback 认知的逐渐深入实际上也代表着对 React 本身这个机制的理解更进一步,因此在你彻底消化 React 刷新机制之前,这个过程中的每一个知识点可能都有巨大的探讨空间wi728资讯网——每日最新资讯28at.com

前几天我的一位学生跟我探讨了一种 useCallback 的用法,他的想法是:当我们在封装开源工具库时,对自定义 hook 中暴露出来的钩子函数使用 useCallback 缓存。因为我们并不确定使用者是否需要一个引用稳定的钩子函数,他们有可能是需要的,因此用 useCallback 来包一层是有意义的。但是他并不确定这样的做法是否合适,是否具备较大的正向收益。wi728资讯网——每日最新资讯28at.com

那么我就借着这个案例,来跟大家探讨一下,我们在 React 进阶的过程中,使用 useCallback 的四个阶段。wi728资讯网——每日最新资讯28at.com

阶段一:敬畏

这个时候你还是一个初学者,对 React 的理解还不够深刻不够全面,但是常常看到文章,或者听别人说 useCallback 跟性能优化有关,可对于你而言,你并不是非常清楚它跟性能优化的具体关系在哪里,想知道,但不知道或者不够确定,因此对这个 hook api 有一种敬畏之心,各个论坛里对于 useCallback 的介绍很多很嘲杂,但你不敢随便用。wi728资讯网——每日最新资讯28at.com

因此你很想去看看别人的代码里,useCallback 是怎么用的,是在什么场景下使用的,但是想要看到别人的代码也并不容易,因此你可能会在这个阶段徘徊。wi728资讯网——每日最新资讯28at.com

阶段二:懂了

随着学习的深入,你逐渐开始深入理解了 React 的单向数据流机制,也对 React 的使用更加熟练,知道 React 经常会存在许多 re-render,你终于搞懂了 useCallback 的使用场景,它结合 React.memo 能够缓存组件,避免组件的冗余 re-render。wi728资讯网——每日最新资讯28at.com

于是你在项目中大量的使用了他们,就像当初 PureComponent 一样,你恨不得每个函数都用 useCallback 套一层,以确保自己的项目能最大限度减少 re-render,从而达到一个极致的性能体验。wi728资讯网——每日最新资讯28at.com

function App() {  ...  const clickHankler = useCallback(() => {    ...  }, [count])  const onOpen = useCallback(() => {    ...  }, [])    ...}

但是不管你用还不用,是大量使用还是大量不使用,从页面的运行结果中,都看不出来你这样写带来了什么实质的提升,甚至你有可能在依赖项的使用上感到难受,因为闭包的影响导致实际运行结果跟你预想的有出入。但是你能明确感受到 re-render 次数减少了。因此这个阶段你非常坚信自己达到了性能优化的目的。wi728资讯网——每日最新资讯28at.com

直到一次偶然的面试中,你被面试官一个问题问得哑口无言:只用 useCallback 能达到减少 re-render 的次数吗?为什么?wi728资讯网——每日最新资讯28at.com

阶段三:精通wi728资讯网——每日最新资讯28at.com

听了我的直播分享,彻底搞懂了 React 的底层 DIFF 机制,你发现原来在 React 底层机制的逻辑下,我们大量的缓存工作其实是没有必要的。React.memo 也有不小的使用成本,有的时候他的损耗不一定比 re-render 更低,于是你懂得了如何在项目中合理的使用 useCallback + React.memo,一通优化下来,项目里的 useCallback 都被删得差不多了,只在关键位置剩下几个。wi728资讯网——每日最新资讯28at.com

优化的结果很理想,re-render 的情况不仅没有变多,项目还减负了,性能又得到了提升,你很开心很有成就感。心想我终于又有了成长,再次遇到上次那个面试官,我必定能吊打他。wi728资讯网——每日最新资讯28at.com

阶段四:贯通wi728资讯网——每日最新资讯28at.com

你终于明白了 useCallback 只是一个非常普通的记忆函数。在 React hooks 特定的机制下记忆函数本身就被大量运用。React 的许多 hook 都有类似的记忆能力,useCallback 只是最普通的那一个,另外的 hook 都在记忆能力的基础之上又添加了一些别的语义。wi728资讯网——每日最新资讯28at.com

useStateuseEffectuseLayoutEffectuseCallbackuseMemouseRefuseReduceruseSyncExternalStore...

这个阶段你不再特殊看待他,在你的知识结构里面你也不再特意的把他跟性能优化挂上勾,而是把他标记为一个记忆函数,他能够保持一个函数的引用,当你在 React 这个不稳定的上下文环境中过,需要一个稳定的引用时,你才会使用 useCallback。wi728资讯网——每日最新资讯28at.com

因此,当你在封装一个开源工具库时,你想到了你会对外抛出一个钩子函数,但是你并不确定使用者会如何使用这个钩子函数,使用者有可能会把他传递给子组件,此时如果钩子函数引用不稳,那么就有可能导致子组件 re-render。wi728资讯网——每日最新资讯28at.com

例如在我们前面学习自定义 hook 的文章中,我们封装了一个 hook useFetch,代码如下:wi728资讯网——每日最新资讯28at.com

import { useState, useRef, useLayoutEffect } from 'react'type API<T, P> = (param?: P) => Promise<T>export default function useFetch<T, P>(api: API<T, P>) {  const param = useRef<P>()  const [list, setList] = useState<T>()  const [error, setError] = useState('')  const [loading, setLoading] = useState(true)  function getList() {    api(param.current).then(res => {      setList(res)      setLoading(false)      setError('')    }).catch(err => {      setLoading(false)      setError(err)    })  }  useLayoutEffect(() => {    loading && getList()  }, [loading])  return {     param,     setParam: (p: P) => param.current = p,    list,     error,     loading,     setLoading   }}

我们可以看到代码里,在这个自定义 hook 中,返回了两个钩子函数 setLoading setParam。wi728资讯网——每日最新资讯28at.com

为了验证他们的引用是否稳定,我们在使用 useFectch 的组件中使用如下代码来验证函数的引用是否发生了变化。wi728资讯网——每日最新资讯28at.com

useEffect(() => {  console.log('setLoading')}, [setLoading])

验证结果非常神奇,setLoading 的引用居然非常的稳定。但对于此时的你来说,这并没有什么值得奇怪的地方。因为他是直接从 useState 中获取出来的。useState 本身就具备记忆能力,因此对于 setLoading 来说,我们不再需要想任何办法来让他的引用来保持稳定。wi728资讯网——每日最新资讯28at.com

setParam 跟预期一样,一点也不稳定,每次状态变化,他的引用都会发生变化。因为在定义它的时候,每次都是新生成的函数给他赋值。wi728资讯网——每日最新资讯28at.com

return {     param, +    setParam: (p: P) => param.current = p,    list,     error,     loading,     setLoading   }

此时到了 useCallback 大展身手的时候了,我们使用 useCallback 包一层。wi728资讯网——每日最新资讯28at.com

return {     param, -    setParam: (p: P) => param.current = p,+    setParam: useCallback((p: P) => param.current = p, []),    list,     error,     loading,     setLoading   }

再次验证,发现引用果然变稳定了。nice。wi728资讯网——每日最新资讯28at.com

但是你害怕这样做有什么你没想到的点,因为 useCallback 太善变了,所以你就跑来跟我沟通,想确定一下这样子做到底能不能带来很大的正向收益。wi728资讯网——每日最新资讯28at.com

万万没想到,我一开口就说:没必要。wi728资讯网——每日最新资讯28at.com

我引导你去看一下引用稳定的 setLoading 是如何使用的,你就去翻了一下代码,结果一看,坏事了,setLoading 因为传了一个参数,导致在使用的时候又套了一层函数。wi728资讯网——每日最新资讯28at.com

代码如下。此时 onClick 接收到的还是一个引用不稳定的匿名函数... setLoading 的引用白考虑了。wi728资讯网——每日最新资讯28at.com

<Button  className={s.button}  onClick={() => setLoading(true)}>

然后你又看了一眼 setParam 的使用,还是这么个情况。wi728资讯网——每日最新资讯28at.com

<input  className={s.input}  placeholder="请输入您要搜索的内容"  onChange={(e) => setParam(e.target.value)}/>

最后一想,发现好像 useCallback 又做了无用功。wi728资讯网——每日最新资讯28at.com

至此,你彻底悟了。wi728资讯网——每日最新资讯28at.com

就说总有一种不确定感,原来少考虑了一步。当自定义 hook 传出来的 函数在执行时需要传入参数时,就不得不在这个函数外面包一层匿名函数,再传递给子组件使用,如果它不需要参数,useCallback 才会发挥它的效果。wi728资讯网——每日最新资讯28at.com

function useRouter() {  const { dispatch } = useContext(RouterStateContext);  const navigate = useCallback((url) => {    dispatch({ type: 'navigate', url });  }, [dispatch]);  const goBack = useCallback(() => {    dispatch({ type: 'back' });  }, [dispatch]);  return {    navigate,    goBack,  };}
const {goBack} = useRouter()... <Child onBack={goBack}  />

当真是真是步步惊心啊。wi728资讯网——每日最新资讯28at.com

你终于悟到了要结合实际使用的场景去考虑使用 useCallback 的准确时机,自此,融汇贯通成就达成。wi728资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-64102-0.htmluseCallback 使用的四个阶段,你都知道吗?

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 掌握Python八种绘图类型带你深入时间序列数据分析

下一篇: 2024年不容错过的十个开发框架

标签:
  • 热门焦点
Top