React Hooks 钩子特性
人在身处逆境时,适应环境的能力实在惊人。人可以忍受不幸,也可以战胜不幸,因为人有着惊人的潜力,只要立志发挥它,就一定能渡过难关。
Hooks 是 React 16.8 的新增特性。它可以让你在不编写 class 组件的情况下使用 state 以及其他的 React 特性。
React Hooks 表现形式是以 use
开头的函数被称为 Hook。useState
是 React 提供的一个内置 Hook。你可以在 React API 参考 中找到其他内置的 Hook。你也可以通过组合现有的 Hook 来编写属于你自己的 Hook。
跟普通函数相比,Hook 比普通函数更为严格。你只能在你的组件(或其他 Hook)的顶层调用 Hook。如果你想在一个条件或循环中使用 useState
,请提取一个新的组件并在组件内部使用它。
为什么要使用 Hooks?
- 解决高阶类组件复用,导致代码层级复杂问题。
- React 组件生命周期的复杂,用于代替生命周期函数。
- 如果一个类一开始设计成了 function 组件,无状态组件,因为需要状态,又改成了 class 成本高。
1. State Hook
State 让组件“记住”用户输入之类的信息。例如,表单组件可以使用 state 来存储输入值,而图片库组件可以使用 state 来存储所选的图像索引。要给组件添加状态,可以使用以下介绍的钩子之一。
1.1 useState
useState 是一个 React Hook。它允许你向组件添加一个状态变量。模拟类组件的状态管理。
// usss
const [state, setstate] = usestate(initialState)
1.2 useReducer
useReducer 是一个 React Hook,它允许你向组件里面添加一个 reducer,来维护一个状态 state。useReducer 不支持异步处理,异步处理需要借助于 Hooks useEffect。
在单个组件中实现状态管理,理解 useReducer 的使用。
const [state, dispatch] = useReducer(reducer, initialState)
其次配合 useContext 跨级通信。将数据逻辑(即状态管理)从组件中(视图逻辑)分离出来通过外部管理,降低组件耦合度。对于单个组件来说意义不大,但是对于多个组件数据需要共享状态的时候很方便。
2. Context Hook
2.1 useContext
useContext 是一个 React Hook,可以让你读取和订阅组件中的 context。减少组件层级,便于组件跨级通信处理。
useContext 配合 useState、useContext 配合 useReducer。
3. Ref Hooks
Refs 允许组件保存一些不用于呈现(渲染)的信息,比如 DOM 节点或超时 ID。与 state 不同,更新 ref 并不会重新呈现组件。React 提供了 useRef hook 来描述。
3.1 useRef
保存引用值:
const ref = useRef(initialValue)
4. Effect Hooks
Effect 允许组件 连接到外部系统并与之同步。这包括处理网络、浏览器、DOM、动画、使用不同 UI 库编写的 widgets 以及其他非 React 代码。
Effect Hooks 副作用钩子函数很好模拟了类组件的生命周期函数。但是我们知道 Function Component 不存在生命周期,所以不要把 Class Component 的生命周期概念搬过夹试图对号入座。
4.1 useEffect
useEffect
是一个 React Hook,它允许你 连接到外部系统并与之同步。
useEffect(setup, dependencies?)
// 处理副作用
useEffect(() => {
// 副作用函数处理代码块 { 比如数据请求,定时器 }
console.log("effect")
return () => {
console.log("cleanup")
// cleanup
// 执行时机:无依赖情况下:组件销毁执行 有依赖情况下:依赖更新和组件销毁的时候
}
}, [ name /* 依赖的状态,如果为空表示不依赖任何状态 */])
依赖:不要对 Dependencies 撒谎,如果你明明在 effect 中使用了某个变量,却没有申明在依赖中,你等于向 React 撒了谎,后果就是,当依赖的变量改变时,useEffect 也不会再次执行,eslint 会报警告。
4.2 useLayoutEffect
useLayoutEffect
是 useEffect
的一个版本,在浏览器重新绘制屏幕之前触发。useLayoutEffect
内部的代码和所有计划的状态更新阻塞了浏览器重新绘制屏幕。如果过度使用,这会使你的应用程序变慢。如果可能的话,尽量选择 useEffect
。
然后如果 Effect 一定要阻止浏览器绘制屏幕,使用 useLayoutEffect
替换 useEffect
。请注意,绝大多数的 Effect 都不需要这样。只有当在浏览器绘制之前运行 Effect 非常重要的时候才需要如此:例如,在用户看到 tooltip 之前测量并定位它。
4.3 useInsertionEffect
useInsertionEffect
是为 CSS-in-JS 库的作者特意打造的。除非你正在使用 CSS-in-JS 库并且需要注入样式,否则你应该使用 useEffect
或者 useLayoutEffect
。
4.4 Effect Hooks 之间区别
简单来说就是调用时机不同,useLayoutEFfect 和原来 componentDidMount & componentDidUpdate 一致,在 React 完成 Dom 更新后马上同步调用的代码,即在浏览器重新绘制屏幕之前触发 useLayouteEffect。你可以在这里测量布局。会阻塞页面渲染(渲染树)。
useEffect 是会在整个页面渲染完才会调用的代码。
useInsertionEffect 会在 React 修改 DOM 之前触发。可以在这里插入动态 CSS。
官方建议优先使用 useEffect
However, we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.
在实际使用时如果想避免页面抖动(即在 useEffect 里修改 Dom 很有可能出现)的话,可以把需要操作 Dom 的代码放在 useLayoutEffect 里。在这里做点 Dom 操作,这些 Dom 修改会和 react 做出的更改一起被一次性渲染到屏幕上,只有一次回流、重绘的代价。
5. 性能 Hook
5.1 useMemo
useMemo
是一个 React Hook,它在每次重新渲染的时候能够缓存、使用计算的结果。
const cachedValue = useMemo(calculateValue, dependencies)
useMemo(() => first, [second])
记忆纯函数计算结果、跳过组件的重新渲染,记忆一个函数。
对于使用 useMemo 记忆一个函数,这看起来很笨拙!记忆函数很常见,React 有一个专门用于此的内置 Hook。将你的函数包装到 useCallback
而不是 useMemo
中,以避免编写额外的嵌套函数。 即 useCallback
的唯一好处是它可以让你避免在内部编写额外的嵌套函数。它没有做任何其他事情。
5.2 useCallBack
useCallback
是一个允许你在多次渲染中缓存函数的 React Hook。记忆函数,防止因为组件重新渲染,导致组件内部定义的方法被重新创建,起到缓存作用。只有第二个参数依赖项变化了才重新声明一次。如果依赖传入空数组,那么函数第一次创建后就被缓存,这时如果函数定义中的使用了某个状态值改变了,以后调用函数拿到的还是老的值。如果不传第二个参数,每次都会重新声明一次。
const cachedFn = useCallback(fn, dependencies)
和 useMemo 唯一的区别是:useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个参数函数并且将函数执行结果返回给你。所以在前面的例子中,可以返回 handleClick 来达到存储函数的目的。
所以 useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而 useMemo 更适合经过函数计算得到一个确定的值,比如记忆组件、结果。
5.3 useTransition
useTransition
允许将状态转换标记为非阻塞,并允许其他更新中断它此更新。
5.4 useDeferredValued
useDeferredValue
是一个 React Hook,可以让你延迟更新 UI 的某些部分。
6. 资源 Hook
use
是一个 React Hook,它可以让你读取类似于 Promise 或 context 的资源的值。
7. 其他 Hook
这些 Hook 主要对库作者有用,而不常用于应用程序代码。
useDebugValue
允许在 React 开发者工具中为自定义 Hook 添加一个标签。
useId
允许组件绑定一个唯一 ID,其通常与可访问性 API 一起使用。
useSyncExternalStore
允许一个组件订阅一个外部 store。
8. 自定义 Hooks
当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。即抽离复用 js 逻辑,让结构更加清晰,符合函数式编程思想。
必须以 'use' 开头吗?必须如此。这个约定非常重要。不遵循的话,由于无法判断某个函数是否包含对其内部 Hook 的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则。官网主要研究以下几个问题:
- 什么是自定义钩子,如何编写自己的钩子;
- 如何命名和结构化自定义的钩子;
- 何时以及为什么要提取自定义钩子;
- 如何在组件之间重用逻辑;