1. useState
略
2. useEffect
2.1 什么是副作用
副作用:纯函数只要和外部进行交互,都可以认为其有副作用
- 引用外部变量;
- 调用外部函数;
宗旨:相同的输入 ==一定会有==> 相同的输出
只要不是在组件渲染时执行的操作,都是副作用操作。
一定会是副作用的操作:
- 修改dom
- 修改全局变量 window
- Ajax 请求
- 计时器
- 存储相关
2.2 useEffect 的调用时机,以及其与 class 组件生命周期的关系
如果你熟悉 React class 的生命周期函数,你可以把
useEffect
Hook 看做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 中直接访问count
state 变量(或其他 props)。我们不需要特殊的 API 来读取它 —— 它已经保存在函数作用域中。Hook 使用了 JavaScript 的闭包机制,而不用在 JavaScript 已经提供了解决方案的情况下,还引入特定的 React API。
useEffect
会在每次渲染后都执行吗?是的,默认情况下,它在第一次渲染之后和每次更新之后都会执行。你可能会更容易接受 effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
如果非要在 hook 中获取 DOM 更新前的状态并直行某些操作,可以使用 useLayoutEffect
,其和 componentDidMount
在表现以及调用时机上 是完全等价的 。
与
componentDidMount
或componentDidUpdate
不同,使用useEffect
调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的useLayoutEffect
Hook 供你使用,其 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"; |