1. useState
略
2. useEffect
2.1 什么是副作用
副作用:纯函数只要和外部进行交互,都可以认为其有副作用
- 引用外部变量;
- 调用外部函数;
宗旨:相同的输入 ==一定会有==> 相同的输出
只要不是在组件渲染时执行的操作,都是副作用操作。
一定会是副作用的操作:
- 修改dom
- 修改全局变量 window
- Ajax 请求
- 计时器
- 存储相关
2.2 useEffect 的调用时机,以及其与 class 组件生命周期的关系
如果你熟悉 React class 的生命周期函数,你可以把
useEffectHook 看做componentDidMount,componentDidUpdate和componentWillUnmount这三个函数的组合。

图片来源:https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
不同于 ClassComponent,当 componentDidMount、componentDidUpdate 执行时,真实 DOM 尚未构建完成;useEffect 是在真实 DOM 构建完成之后执行的,同时 useEffect 是 异步的 。
useEffect做了什么?通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。
为什么在组件内部调用
useEffect?将
useEffect放在组件内部让我们可以在 effect 中直接访问countstate 变量(或其他 props)。我们不需要特殊的 API 来读取它 —— 它已经保存在函数作用域中。Hook 使用了 JavaScript 的闭包机制,而不用在 JavaScript 已经提供了解决方案的情况下,还引入特定的 React API。
useEffect会在每次渲染后都执行吗?是的,默认情况下,它在第一次渲染之后和每次更新之后都会执行。你可能会更容易接受 effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
如果非要在 hook 中获取 DOM 更新前的状态并直行某些操作,可以使用 useLayoutEffect,其和 componentDidMount 在表现以及调用时机上 是完全等价的 。
与
componentDidMount或componentDidUpdate不同,使用useEffect调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的useLayoutEffectHook 供你使用,其 API 与useEffect相同。
useLayoutEffect 是同步的,在 useLayoutEffect 执行的时候也能获取到最新的 DOM 状态,只不过会阻塞以及”打断“ DOM 的渲染,因此应该尽量避免在 useLayoutEffect 中进行复杂的操作,举例来说:
1 | import React, { useEffect, useLayoutEffect, useState } from "react"; |
当我们使用 useEffect 在组件创建后更新文本内容时,屏幕上会显示 “hello world” 后再转变为 “world hello”;
然而当我们使用 useLayoutEffect 在组件创建后更新文本内容时,屏幕上不会显示 “hello world” 而是直接显示 “world hello”,渲染似乎会被 useLayoutEffect 给”打断”掉,起渲染流程如下:

2.3 需要清除的 Effect
当使用 useEffect 时,可以返回一个函数,返回的这个函数被称为 清理函数 。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。
1 | useEffect(()=>{ |
同时,清理函数不仅在上一个 Effect 被清除时执行,在组件卸载时也会执行。
2.4 闭包问题
在前面,我们提到 Hook 使用了闭包机制,我们先看一个示例:
1 | const [count, setCount] = useState(0); |
在上面的示例中,count 会一直为1,这是因为在执行定时器时形成了一个闭包,setCount(count + 1) 获取到的是闭包中的 count,也就是定时器最开始启用时的 count,在定时器执行过程中,是无法获取到最新的 count。同时浏览器也会报出一个警告,提醒开发人员在 useEffect 使用到了一个未声明的依赖项。
解决这个问题有两个办法:
- 将
count作为依赖项传入useEffect中,这样每次都能获取到最新的count。其相当于每次 count 更新后都清除上个定时器并重新创建一个新的定时器;
1 | const [count, setCount] = useState(0); |
setCount传入一个函数,在函数中可以获取到最新的 state,这样就能摆脱闭包;在 React 执行渲染流程时会去执行 setCount 传入的函数,此时传入的 count 是最新的。
1 | const [count, setCount] = useState(0); |
3. useContext
3.1 使用 useContext
可以使用 useContext 来获取上级组件的 Provider 中传入的 value,如下:
1 | const themes = { |
3.2 使用 Consumer
除此之外,我们还可以使用 Context.Consumer 组件来获取 Provider 传入的 value,该组件可以传入一个函数,函数传入一个 value 并返回一个 ReactComponent:
1 | function ThemedButton() { |
4. useMemo
4.1 memo 组件
使用 memo 创建函数组件可以让父组件重新渲染时,子组件不重新渲染,而是只有当子组件的 props 更新时,子组件才会被渲染:
1 | import React, { memo, useState } from "react"; |