常用的Hooks
1. 状态管理:useState/useReducer
useState
- 在函数组件中引入状态,React会在组件渲染时记住当前状态值,触发更新时重新渲染组件
- 状态更新时异步的,多个setState会被批量处理
const [count, setCount] = useState(0)
setCount(count + 1)
setCount(pre => pre + 1)
useReducer
- useState的替代方案,适合复杂状态逻辑或含多子值
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);
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
- 不提供依赖项,每次渲染后都执行
useEffect(() => {
console.log('组件已更新')
})
- 空依赖项数组[]:仅在初始渲染后执行一次
useEffect(() => {
fetchData()
})
- 包含依赖项的数组
useEffect(() ={
fetchUserDate(userId).then(data => setUser(data))
}, [userId])
useEffect
- 渲染阶段(Render) --> Commit(DOM更新) --> 浏览器绘制(Paint) -> useEffect执行(异步执行),不阻塞视觉
useEffect(() => {}, []) --> 仅对应componentDidMount(挂载时执行一次)
useEffect(()=>{},[dep]) --> 对应componentDidMount + componentDidUpdate(挂载执行 + 依赖变化更新)
- 清理函数 --> 对应componentWillUnmount(卸载) + 依赖更新前的清理(类组件无直接对应)
useEffect(() => {
const fetchData = async () => {
const res = await fetch('/api/data');
setData(await res.json())
}
fetchData()
return () => { }
}, [])
useLayoutEffect
- 渲染阶段(Render) -> 提交阶段(Commit,更新DOM) -> useLayoutEffect同步执行(阻塞浏览器绘制) -> 浏览器绘制(Paint),阻塞视觉
- 使用DOM测量,布局调整,避免闪烁
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(() => {
const dom = document.getElementById('box')
dom.style.top = `${dom.offsetHeight}px`;
}, [])
3. 性能优化类
useMemo/React.memo/useCallback
- React组件默认“父组件重渲染 -> 所有子组件重渲染”,这类API核心是缓存,减少无意义的计算/渲染
useMemo
- 让组件中的函数跟随状态更新,缓存一个计算结果(值),只有当依赖项变化时才会重新计算
- 依赖项不变时,直接返回缓存值,不重新执行计算函数
// useMemo: 缓存计算结果
const filteredList = useMemo(() => {
return list.filter(item => item.age > 18)
}, [list])
React.memo
- 高阶组件(HOC),包装函数组件后,对传入的props做浅比较,只有props变化时组件才会渲染,否则复用上次的渲染结果
- 函数props引用变化,导致memo失效
- 最佳配合:当子组件memo包装,且父组件给子组件传递函数props时,必须有个useCallback缓存该函数,才能让memo真正生效
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比较好
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操作、跨渲染状态、缓存上一次的值
const inputRef = useRef(null)
useEffect(() => {
inputRef.current.focus();
}, []) ;
<Input ref={inputRef} />
useContext
- React维护了一个Context上下文栈,组件渲染时会向上遍历栈,找到最近的Context.Provider,读取value并缓存,当Provider的value变化时,所有使用useContext获取该Context的组件都会重渲染
- Context.Provider来确定数据共享范围
- 通过value来分发内容
- 在子组件中通过useContext来获取数据
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指示器
- 复杂更新逻辑
import { useState, useTransition } form 'react'
function SearchPage() {
const handleInputChange = (e) => {
setInputValue(e.target.value)
}
}
useDeferredValue
- 基于useTransitin实现,对一个值创建延迟版本,紧急更新时先用旧值渲染,空闲后再用新值渲染,本质是值的优先级降级
const deferredValue = useEdfeeredValue(value)
- 无法控制状态更新
- 值来自props或第三方hooks
- 父组件控住状态更新逻辑
- 简单值延迟
- 渐进式优化
import { useState, useDeferredValue } from 'react'
function SearchPage(){
const [query, setQuery] = useSState('')
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
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都会独立创建(状态隔离)
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 |