本文旨在介绍常用的Hook的用途和用法。官方文档地址
1. State Hook
-
功能: 在组件的顶层调用
useState
来声明一个 状态变量。-
使用示例
import { useState } from 'react'; // 声明状态变量 const [age, setAge] = useState(28); // 唯一的参数就是初始化原始值。 const [name, setName] = useState('Taylor'); cosnt [fullName, setFullName] = useState({firstName: 'Tom', lastName: 'James'}) const [todos, setTodos] = useState(() => createTodos()) // 用回调的形式填入 createTodos 的返回值 const initArr = () => new Array(9).fill(null) const [list, setList] = useState(initArr) // 把函数本身当参数传入。如果将函数传递给 useState,React 仅在初始化期间调用它。当组件重新渲染,它也不会再次运行。 // 修改state setAge(age => age + 1) // nextState的更新函数。只接受待定的 state 作为其唯一参数,并应返回下一个状态。 setName('Tom') setFullName({...fullName, firstName: 'Jerry'}) // 更新对象 // 更新数组,新增项, 修改项, 删除项。 setTodos([ ...todos, { id: id, title, done: false } ]) setTodos(todos.filter(t => t.id !== todoId)) // 通过过滤返回删除后剩余的项目。 setTodos(tods.map(todo => todo.id !== nextTodo.id ? todo : nextTodo)) // 更新项 // 调用状态变量,每次setAge后,会触发页面的重新渲染。 <div>{age} {fullName.firstName}</div>
-
使用 Immer 来简化写法
import {useImmer} from 'use-immer' const [todos, updateTodos] = useImmer([{name: 'Tom', age: 18}]) updateTods(draft => { const obj = draft.find(item => item.name === 'Tom') obj.age = 19 })
-
使用 key 来重置状态
-
在 渲染列表 时,你经常会遇到
key
属性。然而,它还有另外一个用途。通过向组件传递不同的key
来重置组件的状态。const [count, setCount] = useState(0) const [version, setVersion] = useState(0) setVersion(version + 1) // count更新时视图都会重新渲染,version变化时,不管count有没有变化视图都会重新渲染 <div key={version}>{count}</div>
-
-
存储前一次渲染的信息
- 在极为罕见的情况下,在组件渲染时调用
set
函数来基于已经渲染的值更新状态。
- 在极为罕见的情况下,在组件渲染时调用
-
set函数执行时机,是遇见set函数就推进执行队列,要当前作用域内容执行之后才会进行执行队列进行更新状态值
const [count, setCount] = useState(0) const handleClick = () => { // log(count) 0 setCount(2) // log(count) 0 setTimeout(() => { // log(count) 0 }) } // 解决办法, 使用临时变量 const handleClick = () => { const nextCount = count + 1 setCount(nextCount) // log(count) 0 // log(nextCount) 1 }
在组件的顶层作用域调用
useReducer
以创建一个用于管理状态的 reducer。使用场景:批量更新
state
时候使用。-
useReducer的三个参数
import {useReducer} from 'react' const [state, dispatch] = useReducer(reducer, initialArg, init?)
- reducer: 用于更新 state 的纯函数。参数为 state 和 action,返回值是更新后的 state。state 与 action 可以是任意合法值。
-
initialArg
:用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的init
参数。 -
可选参数
init
:用于计算初始值的函数。如果存在,使用init(initialArg)
的执行结果作为初始值,否则使用initialArg
。
dispatch
函数:useReducer
返回的dispatch
函数允许你更新 state 并触发组件的重新渲染-
使用示例:
import {useReducer, useState} from 'react' cosnt initData = [ {id: 1, name: 'Tom', age: 18}, {id: 2, name: 'Jerry', age: 16} ] /** * 声明reducer函数 * 两个参数,list是当前的state, action 可以是任意合法值,包含了要操作的类型和需要跟新的数据。 * 返回值是 更新后的 state */ const stuReducer (list, action) { switch (action.type) { case 'add': { // 往数组中添加数据 return [ ...list, {id: action.id, name: action.name, age: action.age} ] } case 'update': { // 更新数组中一项的数据 return list.map(item => item.id === action.id ? ({id: action.id, name: action.name, age: action.age}) : item) } case 'delete': { // 过滤掉要删除的数据 return list.filter(item => item.id !== aciton.id) } default: { throw Error ('没有找到对应的' + action.type) } } } const Demo = () => { /** * useReducer三个参数 * 第一个参数:stuReducer 用于更新 state 的纯函数 * 第二个参数:initData 用于初始化 state 的任意值。 * 补丁:第二个参数initData也可以是一个函数的返回值比如formatArr(initData)。但是这样的话每次更新state时候formatArr函数都会被调用,请把formatArr函数本身作为第三个参数传入useReducer中。例如:useReducer(stuReducer, initData, formatArr) * 还有第三个可选参数,这里省略了。是用于计算初始值的函数。 * */ const [list, dispatch] = useReducer(stuReducer, initData) const oprateList = (type) { // 根据不同的type值调用reducer函数返回对应更新后的state. dispatch({ type: type, id: 'xx', name: 'xx', age: 99 }) // dispatch之后打印一下list,发现还是更新之前的数据,这里的原理与 useState 相同。这是因为 state 的行为和快照一样。更新 state 会使用新的值来对组件进行重新渲染,但是不会改变当前执行的事件处理函数里面 state 的值。 } return <div>要渲染的内容以及操作的按钮内容</div> }
-
reducer 和初始化函数运行了两次
2. Context Hook
-
向组件树深层传递数据。与prop的父子组件组件传值相比,context可跨代进行数据传递。并能更新传递的数据。
-
使用示例
// createContext.ts // 第一步要创建context import {createContext} from 'react' const ThemeContext = createContext(null) // 也可以默认主体色 light createContext('light') export default ThemeContext
// 组件中调用context import {useContext, useState} from 'react' import ThemeContext from './createContext' const App = () => { const [theme, setTheme] = useState('dark') /** * value='dark' 修改了 themeContext的值 * 如果这里的 value值 不输入,则取的是 createContext的默认值。 * 下面是通过使用state来初始化和更新 themeContext的值的示例。 * <ThemeContext.Provider value={theme}><Form /></ThemeContext.Provider> * setTheme('light') 会更新themeContext的值 */ return ( <ThemeContext.Provider value='dark'><Form /></ThemeContext.Provider> ) } const Form = () => { const theme = useContext(ThemeContext) const className = `box-${theme}` // box-dark return <div className={className}></div> }
-
补丁: 通过在 provider 中使用不同的值包装树的某个部分,可以覆盖该部分的 context。
<ThemeContext.Provider value="dark"> ... <ThemeContext.Provider value="light"> <Footer /> </ThemeContext.Provider> ... </ThemeContext.Provider>
3. Ref Hook
-
-
引用一个不需要渲染的值,通过ref操作
DOM
,储存定时器 interval ID。const ref = useRef(initialValue) # initialValue:ref 对象的 current 属性的初始值。可以是任意类型的值。这个参数在首次渲染后被忽略。
改变Ref不会触发页面的重新渲染
-
使用示例
import {useRef} from 'react' const MyApp = () => { const inputRef = useRef(null) // 引用input的元素 const intervalRef = useRef(null) const handleClick = () => { const intervalRef = setInterval(() => {}, 10) // 清除定时器 clearInterval(intervalRef.current) // 引用dom inputRef.current.focus() } return <> <input ref={inputRef} /> </> }
-
补丁: 无法获取自定义组件的 ref。使用
forwardRef
来解决。-
- 能让你自定义由 ref 暴露出来的句柄。是
forwardRef
的优化补充。
- 能让你自定义由 ref 暴露出来的句柄。是
4. Effect Hook
-
-
允许你 将组件与外部系统同步。
useEffect(setup, dependencies?)
setup
:处理 Effect 的函数。setup 函数选择性返回一个 清理(cleanup) 函数。当组件被添加到 DOM 的时候,React 将运行 setup 函数。在每次依赖项变更重新渲染后,React 将首先使用旧值运行 cleanup 函数(如果你提供了该函数),然后使用新值运行 setup 函数。在组件从 DOM 中移除后,React 将最后一次运行 cleanup 函数。-
可选
dependencies
:setup
代码中引用的所有响应式值的列表。<font color='red'>响应式值包括 props、state 以及所有直接在组件内部声明的变量和函数</font>。如果你的代码检查工具 配置了 React,那么它将验证是否每个响应式值都被正确地指定为一个依赖项。依赖项列表的元素数量必须是固定的,并且必须像[dep1, dep2, dep3]
这样内联编写。React 将使用Object.is
来比较每个依赖项和它先前的值。如果省略此参数,则在每次重新渲染组件之后,将重新运行 Effect 函数。-
如果指定了依赖项,则 Effect 在 初始渲染后以及依赖项变更的重新渲染后 运行。
useEffect(() => { // ... }, [a, b]); // 如果 a 或 b 不同则会再次运行
-
-
- 如果你的 Effect 确实没有使用任何响应式值,则它仅在 **初始渲染后** 运行。
```jsx
useEffect(() => {
// ...
}, []); // 不会再次运行(开发环境下除外)
```
- 如果完全不传递依赖数组,则 Effect 会在组件的 **每次单独渲染(和重新渲染)之后** 运行。
```jsx
useEffect(() => {
// ...
}); // 总是再次运行
```
-
使用场景
-
使用示例:
-
请求数据
mport { useState, useEffect } from 'react'; import { fetchBio } from './api.js'; export default function Page() { const [person, setPerson] = useState('Alice'); const [bio, setBio] = useState(null); useEffect(() => { let ignore = false; setBio(null); fetchBio(person).then(result => { if (!ignore) { setBio(result); } }); return () => { ignore = true; }; }, [person]);
-
定时器
cosnt [count, setCount] = useState(0) useEffect(() => { const timer = setInterval(() => { setCount(c => c + 1) }, 1000) return () => clearInterval(timer) }, [])
-
-
-
useLayoutEffect
是useEffect
的一个版本,在浏览器重新绘制屏幕之前触发。 可能会影响性能。<font color="red">谨慎使用</font> - 在浏览器重新绘制屏幕前计算布局。
-
-
- 是为 CSS-in-JS 库的作者特意打造的。除非你正在使用 CSS-in-JS 库并且需要注入样式,否则你应该使用
useEffect
或者useLayoutEffect
。
- 是为 CSS-in-JS 库的作者特意打造的。除非你正在使用 CSS-in-JS 库并且需要注入样式,否则你应该使用
5. 性能 Hook
-
useMemo
- 它在每次重新渲染的时候能够缓存计算的结果。
-
useCallback
- 是一个允许你在多次渲染中缓存函数的 React Hook。
-
useTransition
- 是一个帮助你在不阻塞 UI 的情况下更新状态的 React Hook。
- 它允许你在某些状态更新时实现过渡效果,即这些更新可以有不同的优先级,从而可以延迟一些不那么重要的更新,以便更快速地响应更关键的用户交互。这个Hook的主要作用是在状态更新时,提供了一种方式来“标记”某些更新为“过渡”,这意味着这些更新可能会延迟,以便React可以先处理其他更高优先级的更新,如用户的点击事件。
- 常用于优化视图切换时的用户体验。例如,当某个组件的渲染特别耗时,如Movie组件,如果在渲染该组件期间页面的UI被阻塞,用户会感觉页面卡顿。通过使用useTransition,可以优化这种情况下的用户体验。此外,useTransition还可以帮助你控制过渡效果的持续时间和延迟时间等参数,使得用户可以看到过渡效果,而不是直接看到新的组件,从而提高用户体验。
-
useDeferredValue
- 可以让你延迟更新 UI 的某些部分。
- 可以将
useDeferredValue
作为性能优化的手段。当你的 UI 某个部分重新渲染很慢、没有简单的优化方法,同时你又希望避免它阻塞其他 UI 的渲染时,使用useDeferredValue
很有帮助。
6. 资源 Hook
7. 其他 Hook
- 使用
useDebugValue
自定义 React 开发者工具为自定义 Hook 添加的标签。 - 使用
useId
将唯一的 ID 与组件相关联,其通常与可访问性 API 一起使用。 - 使用
useSyncExternalStore
订阅外部 store。
8. 自定义 Hook
- 开发者可以 自定义 Hook 作为 JavaScript 函数。