React Hooks 核心原理与实战
王沛
eBay 中国研发中心资深技术专家
10740 人已学习
新⼈⾸单¥59
登录后,你可以任选2讲全文学习
课程目录
已完结/共 25 讲
React Hooks 核心原理与实战
15
15
1.0x
00:00/00:00
登录|注册

04|内置 Hooks(2):为什么要避免重复定义回调函数?

你好,我是王沛。这节课我们来继续学习内置 Hooks 的用法。
在上节课你已经看到了 useState 和 useEffect 这两个最为核心的 Hooks 的用法。理解了它们,你基本上就掌握了 React 函数组件的开发思路。
但是还有一些细节问题,例如事件处理函数会被重复定义、数据计算过程没有缓存等,还都需要一些机制来处理。所以在这节课,你会看到其它四个最为常用的内置 Hooks (包括 useCallback、useMemo、useRef 和 useContext)的作用和用法,以及如何利用这些 Hooks 进行功能开发。

useCallback:缓存回调函数

在 React 函数组件中,每一次 UI 的变化,都是通过重新执行整个函数来完成的,这和传统的 Class 组件有很大区别:函数组件中并没有一个直接的方式在多次渲染之间维持一个状态。
比如下面的代码中,我们在加号按钮上定义了一个事件处理函数,用来让计数器加 1。但是因为定义是在函数组件内部,因此在多次渲染之间,是无法重用 handleIncrement 这个函数的,而是每次都需要创建一个新的:
function Counter() {
const [count, setCount] = useState(0);
const handleIncrement = () => setCount(count + 1);
// ...
return <button onClick={handleIncrement}>+</button>
}
你不妨思考下这个过程。每次组件状态发生变化的时候,函数组件实际上都会重新执行一遍。在每次执行的时候,实际上都会创建一个新的事件处理函数 handleIncrement。这个事件处理函数中呢,包含了 count 这个变量的闭包,以确保每次能够得到正确的结果。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

React内置Hooks useCallback、useMemo、useRef和useContext的用法及作用。通过实际例子生动地阐述了它们的使用场景和优势。useCallback和useMemo的作用是缓存回调函数和计算结果,避免不必要的函数重复创建和组件重新渲染。useRef用于在函数组件之间共享数据,以及保存DOM节点的引用。而useContext则能定义全局状态,实现跨层次或同层组件之间的数据共享。文章还提到了Context的灵活性和一些注意事项。总结来说,本文详细介绍了React内置Hooks的使用方法和原理,对于React函数组件的性能优化和全局状态管理具有重要的指导意义。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《React Hooks 核心原理与实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(50)

  • 最新
  • 精选
  • 何以解忧
    置顶
    关于子组件props 不变,可以减少不必要的渲染问题,不是特别理解。似乎只要父组件重新渲染子组件必然重新渲染,是内部有什么别的地方优化么?

    作者回复: 好问题,之前在 Class 组件中可以把组件继承自 React.PureComponent,从而 props 没变就不重新 render。现在函数组件没有 PureComponent的概念,但是提供了 React.memo (https://reactjs.org/docs/react-api.html#reactmemo) 这样一个高阶组件,可以让任何 React 组件都能在 props 不变时就不重新渲染。所以,在开发过程中,即使现在没有使用 React.memo,但是使用 useCallback 或者 useMemo 至少可以为性能优化提供一个基础。

    2021-06-10
    9
    17
  • 满月
    我们能否用 state 去保存 window.setInterval() 返回的 timer 呢? 我理解的是可以,只是没有 useRef 更优,因为在更新 state 值后会导致重新渲染,而 ref 值发生变化时,是不会触发组件的重新渲染的,这也是 useRef 区别于 useState 的地方。

    作者回复: 100分~

    2021-06-01
    3
    68
  • 桃翁
    useRef 如果只是用来 在多次渲染之间共享数据,是不是直接可以把变量定义到组件外面,这样也可以达到目的,感觉还更方便一点呢。

    作者回复: useRef 可以保证这个变量只在当前组件的实例中使用。也就是说,如果一个组件页面上有多个实例,比如: <div><Timer /><Timer /></div> 那么组件外的普通变量是被 Timer 共享的,就会产生问题。

    2021-06-01
    5
    29
  • cyh41
    是任何场景 函数都用useCallback 包裹吗?那种轻量的函数是不是不需要?

    作者回复: 确实不是,useCallback 可以减少不必要的渲染,主要体现在将回调函数作为属性传给某个组件。如果每次都不一样就会造成组件的重新渲染。但是如果你确定子组件多次渲染也没有太大问题,特别是原生的组件,比如 button,那么不用 useCallback 也问题不大。所以这和子组件的实现相关,和函数是否轻量无关。但是比较好的实践是都 useCallback。

    2021-06-01
    4
    15
  • 七月有风
    问下老师,useCallback、useMemo 和 useEffect的依赖机制一样吗?都是浅比较吗?

    作者回复: 是的,所以依赖比较都是浅比较

    2021-06-03
    2
    6
  • Geek_71adef
    1 useState 实现组件共享,考虑到组件之间的通信 2 state 去保存的话 会造成异步渲染 造成无限循环

    作者回复: 只有需要触发 UI 更新的状态才需要放到 state 里。这里的 timer 其实只是临时存放一个变量,无需用 state 保存。否则会造成不必要的渲染。

    2021-06-01
    6
  • 院长。
    有个问题想问下,关于useMemo,文档说的是性能优化的保证,也就是涉及到大量计算的时候可以使用,因为依赖项的比较本身也是有开销的。 那如果我就只是很简单的计算,或者就只是返回一个固定的对象,有必要使用吗

    作者回复: 依赖项比较大的性能开销可以忽略。useMemo 其实除了解决自身计算的性能问题之外,还有就是可以避免 接收这个数据的组件过多的重新渲染,以及依赖这个数据的其它 hooks 多余的计算。所以即使简单的计算,最好也是用 useMemo。

    2021-06-11
    4
  • 闲闲
    useCallBack依赖是空数组表示什么?

    作者回复: 没有意义,相当于每次都创建一个新的函数

    2021-06-01
    7
    4
  • 开开之之
    老师,我也有同样的疑问,定时器的例子,不能用一个常量去保存吗? import React, { useState, useCallback, useRef } from 'react' export default function Timer() { const [time, setTime] = useState(0) const timer = useRef(null) let timer2 = null const handleStart = useCallback(() => { timer2 = window.setInterval(() => { // 这里是个闭包,每次拿到的time值是0,所以要这样写手动去更新time的值 setTime((time) => time + 1) }, 1000) }, [time]) const handleStop = useCallback(() => { window.clearInterval(timer2) timer2 = null }, []) return ( <div> {time / 10} seconds. <br/> <button onClick={handleStart}>start</button> <button onClick={handleStop}>stop</button> </div> ) }

    作者回复: 你这个写法,看上去能够 work 是因为 handleStop 没有将 timer2 设为依赖项,eslint 会报错。其实在你第二次 start 后就无法 stop 了。因为 timer 一直是第一次的值。

    2021-06-06
    5
    2
  • 琼斯基亚
    老师,请问: const handleIncrement = useCallback(() => setCount(count + 1), [count]); const handleIncrement = useCallback(() => setCount(q => q + 1), []); 在性能方面是否后者优于前者?我的理解: 后者只创建了一次函数,但是又调用了多次在setCount的回调函数 前者只会在count变化的时候创建新的回调函数 这样分析下来我又觉得两者没什么差异 我不是太清楚这两者的优缺点,希望得到老师的解答。

    作者回复: 严格来说,后者确实优于前者,因为后者在 count 变化时不会创建新的 handleIncrement 这样的 callback,这样接收这个属性的组件就不需要重新刷新。但是对于简单的场景,可以忽略这种差异。

    2021-06-03
    4
    1
收起评论
显示
设置
留言
50
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部