常用的Hooks
1. 状态管理:useState/useReducer
useState
- 在函数组件中引入状态,React会在组件渲染时记住当前状态值,触发更新时重新渲染组件
- 状态更新时异步的,多个setState会被批量处理
scss
const [count, setCount] = useState(0)
// 直接更新
setCount(count + 1)
// 函数式更新(基于前一个状态)
setCount(pre => pre + 1)
useReducer
- useState的替代方案,适合复杂状态逻辑或含多子值
go
// 表单状态机(状态转换复杂)
const formReducer = (state, action) => {
switch(action.type) {
case 'field_change':
return { ...state, [action.field]: action.value, dirty: true}
case 'validate':
return { ...state, errors: validate(state), valid: !hasErrors};
case 'submit':
return state.valid ? { ...state, submitting: true } : state;
default:
return state;
}
}
const [formState, dispatch] = useReducer(formReducer, initialState);
// dispatch({type: 'field_change', field:'email', value:'a@b.com'})
2. 副作用处理类
useEffect/useLayoutEffect
-
组件首次渲染后,执行副作用函数
-
组件卸载时,执行最后一次的清理函数
-
工作原理
- 保存上一次的依赖项,React在内部保存当前的依赖项数组
- 浅比较(Shallow Comparison)使用Object.is()算法逐项比较
Object.is(oldUserId, newUserId) - 任何一项变化执行effect
- 基本类型值比较,引用类型按引用比较,浅比较不检查对象内部属性
- Object.is()与===相似
- NAN = NAN false; +0 === -0 true
object.is(NaN, NaN) true, object.is(+0, -0) false
-
不提供依赖项,每次渲染后都执行
- 可能导致不必要的性能开销
javascriptuseEffect(() => { console.log('组件已更新') }) -
空依赖项数组[]:仅在初始渲染后执行一次
- 初始化操作,一次性设置
scssuseEffect(() => { fetchData() // 从服务器获取初始数据 }) -
包含依赖项的数组
- 响应特定数据变化
scssuseEffect(() ={ fetchUserDate(userId).then(data => setUser(data)) }, [userId]) // 依赖于userId
useEffect
- 渲染阶段(Render) --> Commit(DOM更新) --> 浏览器绘制(Paint) -> useEffect执行(异步执行),不阻塞视觉
useEffect(() => {}, [])--> 仅对应componentDidMount(挂载时执行一次)useEffect(()=>{},[dep])--> 对应componentDidMount + componentDidUpdate(挂载执行 + 依赖变化更新)- 清理函数 --> 对应componentWillUnmount(卸载) + 依赖更新前的清理(类组件无直接对应)
scss
// useEffect:异步副作用(数据请求)
useEffect(() => {
const fetchData = async () => {
const res = await fetch('/api/data');
setData(await res.json())
}
fetchData()
return () => { /* 清理逻辑(如取消请求)*/}
}, [])
useLayoutEffect
- 渲染阶段(Render) -> 提交阶段(Commit,更新DOM) -> useLayoutEffect同步执行(阻塞浏览器绘制) -> 浏览器绘制(Paint),阻塞视觉
- 使用DOM测量,布局调整,避免闪烁
javascript
const [num, setNum] = useState(1)
useLayoutEffect(() => {
// 定义可复用的函数引用(解决事件监听移除无效问题)
const handleFn = () { console.log('自定义事件fn触发') }
document.body.addEventListener('fn', handleFn)
console.log('useLayoutEffect, 打印顺序在useEffect之前')
return () => {
document.body.removeEventListener('fn', handleFn)
}
}, [num])
// useLayoutEffect: 同步DOM操作(避免闪烁)
useLayoutEffect(() => {
const dom = document.getElementById('box')
dom.style.top = `${dom.offsetHeight}px`; // 同步调整位置
}, [])
3. 性能优化类
useMemo/React.memo/useCallback
- React组件默认"父组件重渲染 -> 所有子组件重渲染",这类API核心是缓存,减少无意义的计算/渲染
useMemo
- 让组件中的函数跟随状态更新,缓存一个计算结果(值),只有当依赖项变化时才会重新计算
- 依赖项不变时,直接返回缓存值,不重新执行计算函数
ini
// useMemo: 缓存计算结果
const filteredList = useMemo(() => {
return list.filter(item => item.age > 18); // 仅list变化时重新计算
}, [list])
React.memo
- 高阶组件(HOC),包装函数组件后,对传入的props做浅比较,只有props变化时组件才会渲染,否则复用上次的渲染结果
- 函数props引用变化,导致memo失效
- 最佳配合:当子组件memo包装,且父组件给子组件传递函数props时,必须有个useCallback缓存该函数,才能让memo真正生效
ini
import React, { memo, useState } from 'react;
// 子组件:用memo包装,期望props不变时不重渲染
const Child = memo(({onClick, name}) => {
console.log('Child组件重渲染了')
return <button onClick={onClick}>{name}</button>
})
// 子组件props 只有基本类型,memo单独用就够了
const Child1 = memo(({age}) => {
console.log('Child重渲染')
return <div>年龄:{age}</div>
})
// 父组件
const Parent =() => {
const [count, setCount] = useState(0)
const age = 20; // 基本类型,引用不变
// 问题:每次父组件渲染,都会创建新的handleClick函数(引用变了)
const handleClick = () => {
console.log('点击了')
}
return (
<div>
<button onClick={() => setCount(count + 1)}>计数:{count} </button>
{/* 即使name不变,handleClick引用变了 -> memo 认为props变了 -> 子组件重渲染 */}
<Child onClick={handleClick} name="按钮" />
<Child1 age={age} /> {/* age不变 -> 子组件不重渲染*/}
</div>
)
}
// 对上面进行局部改造可解决问题
// 关键:用useCallback缓存函数,依赖为空-> 永远返回统一个引用
const handleClick = useCallback(() => {
console.log('点击了');
},[]) // 依赖项数组:无依赖 -> 函数引用永不变化
{/*onClick 引用不变,name不变 -> memo 生效,子组件不重渲染 */}
<Child onClick={handleClick} name='按钮' />
useCallback
- 跟随状态更新函数,缓存函数引用,即函数本身,而不是函数的执行结果
- 依赖项不变时,返回同一个函数地址,而非每次渲染创建新函数
- useMemo(() => fn, deps)相当于useCallback(fn, deps)
- 在使用方法上,useCallback与useMemo相同,useMemo返回的是一个值,useCallback返回的是个函数
- 给子组件传值时候用useCallback比较好
javascript
const [num, setNum] = useState(1)
const getDoubleNum = useCallback(() => {
return num * 2
}, [num])
return (<div>
{ getDoubleNum()}
<Child callback={getDoubleNum}></Child>
</div>)
function Child(props) {
useEffect(() => {
console.log('callback更新了')
}, [props.callback])
}
4. 引用/通信类:useRef/useContext
useRef
- 一个持久化的Ref对象(Fiber节点的Ref属性),ref.current不受组件重渲染影响,且修改它不会触发组件重渲染(因为不进入状态队列)
- 获取DOM时,React会在DOM挂载后将元素赋值给ref.current
- 三大应用场景:DOM操作、跨渲染状态、缓存上一次的值
scss
// useRef: 存DOM/跨渲染变量
const inputRef = useRef(null)
useEffect(() => {
inputRef.current.focus(); // 获取DOM并聚焦
}, []) ;
<Input ref={inputRef} />
useContext
- React维护了一个Context上下文栈,组件渲染时会向上遍历栈,找到最近的Context.Provider,读取value并缓存,当Provider的value变化时,所有使用useContext获取该Context的组件都会重渲染
- Context.Provider来确定数据共享范围
- 通过value来分发内容
- 在子组件中通过useContext来获取数据
javascript
const Context = createContext(null)
function StateFun() {
const [num, setNum] = useState(1)
return (
<div>
这是一个函数组件{ num }
<Context.Provider value={num}>
<Item1/>
<Item2/>
</Context.Provider>
</div>
)
}
function Item1(){
const num = useContext(Context)
return <div>子组件1{num}</div>
}
function Item2(){
const num = useContext(Context)
return <div>子组件2{num}</div>
}
5. 渲染优化类
useTransition/useDerredValue
- React的优先级调度机制:将更新分为"紧急更新"(如输入框、点击)和非紧急更新(如大数据渲染)
useTransition
- 标记一个状态更新为非紧急,React会优先处理紧急更新,等主线程空闲后再执行非紧急更新,期间页面保持响应
const [ispending, startTransition] = useTransition()- ispending: 布尔值,指示过渡状态是否进行中
- startTransition:函数,用于包裹优先级状态更新
- 高优先级:直接调用状态更新函数
- 低优先级:用startTransition包裹状态更新
- 可控制状态更新
- 能够修改setState调用
- 事件处理函数在当前组件内
- 需要加载状态
- ispending提供明确的加载反馈
- 适合显示Loading指示器
- 复杂更新逻辑
- 多个状态需要同时更新
- 需要精确控制更新时机
javascript
// 示例
import { useState, useTransition } form 'react'
function SearchPage() {
const handleInputChange = (e) => {
setInputValue(e.target.value)
}
}
useDeferredValue
- 基于useTransitin实现,对一个值创建延迟版本,紧急更新时先用旧值渲染,空闲后再用新值渲染,本质是值的优先级降级
const deferredValue = useEdfeeredValue(value)- 无法控制状态更新
- 值来自props或第三方hooks
- 父组件控住状态更新逻辑
- 简单值延迟
- 只需要延迟某个值的使用
- 不需要复杂的状态管理
- 渐进式优化
- 最小代码改的
- 快速应用到现有组件
javascript
// 示例
import { useState, useDeferredValue } from 'react'
function SearchPage(){
const [query, setQuery] = useSState('')
// 将query包装成延迟值
const defeeredQuery = useDeferredValue(query)
const handleInputChange = (e) => {
setQuery(e.target.value)
}
// 判断是否处于过时状态
const isState = query !== defeeredQuery
return (
<div>
<input type='text' value={query} onChange={handleInputChange} />
<div style={{ opacity: isStatle ? 0.5 : 1 }}>
<SearchResultList query={defeeredQuery} />
</div>
</div>
)
}
6. 工具类:AbortController
- 是浏览器原生API(非React专属),核心是发布-订阅模式
- 创建控制器生成一个AbortSignal信号
- 将信号绑定到异步操作(如fetch),异步操作会监听信号状态
- 调用controller.abort()时,信号触发中止事件,异步操作会立即中止并抛出AbortError
scss
useEffect(() => {
const controller = new AbotController()
const fetchData = async () => {
try{
const reponse = await fetch(url, {signal: controller.signal})
} catch (error) {
if(error.name !== 'AbortError'){
// 处理错误逻辑
}
}
}
fetchData()
return () => controller.abort() // 中止操作
}, [dependencies])
7. 自定义Hooks
- 自定义Hooks不是React提供的内置API,而是基于内置Hooks封装的复用逻辑
- 本质是遵循命名规则的普通函数(必须用use开头)
- 内部可以调用任意内置Hooks(React会通过调用栈识别Hooks归属的组件)
- 每次调用自定义Hooks,其中的内置Hooks都会独立创建(状态隔离)
scss
// 自定义hook:封装请求逻辑
function useRequest(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
});
return () => controller.abort();
}, [url]);
return { data, loading };
}
// 组件中使用
const { data, loading } = useRequest('/api/data');
核心区别总览表
| 类别 | API | 核心定位 | 关键特性 |
|---|---|---|---|
| 状态管理 | useState | 简单局部状态 | 异步更新、语法简洁 |
| 状态管理 | useReducer | 复杂状态逻辑 | 集中更新、纯函数reducer |
| 副作用 | useEffect | 异步副作用 | 绘制后执行、不阻塞 |
| 副作用 | useLayoutEffect | 异步副作用 | 绘制前执行、阻塞 |
| 性能优化 | useMemo | 缓存计算值 | 依赖变化才重新计算 |
| 性能优化 | useCallback | 缓存函数引用 | 避免子组件无意义重渲染 |
| 性能优化 | React.memo | 缓存组件渲染结果 | 浅比较props |
| 引用/通信 | useRef | 持久化引用(DOM/变量) | 修改不触发重渲染 |
| 引用/通信 | useContext | 跨组件传智 | 依赖Provider传递至 |
| 渲染优化 | useTransition | 标记非紧急更新 | 优先级调度、不阻塞紧急操作 |
| 渲染优化 | useDeferredValue | 延迟值更新 | 基于useTransition实现 |
| 工具 | AbortController | 取消异步操作 | 浏览器原生、监听中止信号 |
| 逻辑复用 | 自定义Hooks | 封装复用逻辑 | 以use开头、可调用内置Hooks |