React Hooks 是React 16.8+ 引入的核心特性,彻底替代了类组件,让函数组件拥有状态管理、副作用处理、性能优化等能力。本文整理了React 18+ 全部内置Hooks,按「基础必用、副作用、状态进阶、引用、上下文、性能优化、并发、工具类」分类,每个Hook都包含「核心作用、语法、实战示例、适用场景、注意事项」,覆盖日常开发99%的场景,新手可直接套用,老手可查漏补缺。
核心原则:所有Hooks仅能在「函数组件顶层」或「自定义Hook顶层」调用,不能在条件判断、循环、嵌套函数中调用(React通过调用顺序识别Hooks,避免逻辑混乱)。
一、基础必用Hooks(入门必备,100%高频)
这类Hooks是React开发的基础,几乎所有函数组件都会用到,必须熟练掌握。
1. useState ------ 函数组件状态管理(核心)
核心作用
给函数组件添加响应式状态,返回「当前状态值 + 状态更新函数」,是最基础、最常用的Hook。
语法
scss
// 基础用法:初始值直接传入
const [state, setState] = useState(initialState);
// 函数式初始值:仅首次渲染执行(适合昂贵计算,避免每次渲染重复计算)
const [state, setState] = useState(() => {
return computeInitialValue(); // 昂贵计算逻辑
});
// 函数式更新:依赖上一次的状态值(避免闭包陷阱)
setState(prevState => newState);
实战示例
javascript
import { useState } from 'react';
// 示例1:简单计数器(基础状态)
function Counter() {
// 声明count状态,初始值为0,setCount为更新函数
const [count, setCount] = useState(0);
// 函数式更新:依赖上一次的count值,避免闭包问题
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => prev - 1);
return (
当前计数:{count}
<button onClick={increment}>+1<button onClick={-1
);
}
// 示例2:对象状态(需注意更新方式)
function UserForm() {
// 声明对象类型状态
const [user, setUser] = useState({ name: '', age: 18 });
// 对象更新:必须返回新对象(不能直接修改原对象,React无法检测变化)
const updateName = (e) => {
setUser(prev => ({ ...prev, name: e.target.value }));
};
return (
<input value={姓名" />
姓名:{user.name},年龄:{user.age}
);
}
适用场景
简单响应式状态管理:计数器、开关(布尔值)、输入框值、弹窗显示/隐藏、简单数组/对象状态。
注意事项
- 状态更新是「替换而非合并」:如果状态是对象/数组,必须返回新引用(使用扩展运算符
...、map、filter等),不能直接修改原状态(如user.name = 'xxx'无效)。 - 初始值仅首次渲染生效:即使初始值是函数,也只会在组件首次挂载时执行一次,后续渲染不会重复执行。
- 避免闭包陷阱:如果更新状态依赖上一次的状态值(如计数器累加),必须使用「函数式更新」(
prev => prev + 1),否则可能获取到旧状态。 - 状态更新是异步的:React会批量处理状态更新,在同一个事件处理函数中多次调用
setState,只会触发一次渲染(如setCount(1); setCount(2);最终只显示2)。
2. useEffect ------ 副作用处理(核心)
核心作用
处理函数组件的「副作用」,替代类组件的componentDidMount(挂载)、componentDidUpdate(更新)、componentWillUnmount(卸载)三个生命周期钩子,统一管理副作用逻辑。
常见副作用:数据请求、DOM操作、定时器/计时器、事件监听、订阅(如WebSocket)等。
语法
scss
// setup:副作用函数,执行副作用逻辑
// dependencies:依赖数组,控制setup函数的执行时机(可选)
useEffect(setup, dependencies?);
// setup函数可返回一个「清理函数」,用于组件卸载/依赖变化时清理副作用
useEffect(() => {
// 副作用逻辑(如请求、定时器)
return () => {
// 清理逻辑(如取消请求、清除定时器、移除事件监听)
};
}, [dependencies]);
执行时机(关键)
- 不传递依赖数组(
useEffect(setup)):每次组件渲染(挂载+更新)都会执行setup函数,慎用(性能较差)。 - 传递空依赖数组(
useEffect(setup, [])):仅在组件挂载时执行一次setup函数,卸载时执行清理函数(对应componentDidMount + componentWillUnmount)。 - 传递依赖数组(
useEffect(setup, [a, b])):组件挂载时执行一次,后续只有当依赖数组中的a或b发生变化时,才会执行setup函数(对应componentDidMount + componentDidUpdate),卸载时执行清理函数。
实战示例
scss
import { useState, useEffect } from 'react';
// 示例1:数据请求(挂载时请求,卸载时取消请求)
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 创建AbortController,用于取消请求
const controller = new AbortController();
const signal = controller.signal;
// 异步请求函数
const fetchUsers = async () => {
try {
const res = await fetch('/api/users', { signal });
const data = await res.json();
setUsers(data);
} catch (err) {
// 忽略取消请求导致的错误
if (err.name !== 'AbortError') {
console.error('请求失败:', err);
}
} finally {
setLoading(false);
}
};
fetchUsers();
// 清理函数:组件卸载时取消请求
return () => controller.abort();
}, []); // 空依赖,仅挂载执行
if (loading) return 加载中...;
return (
{users.map(user => (
<div key={{user.name}
))}
);
}
// 示例2:定时器(挂载时启动,卸载时清除)
function Timer() {
const [time, setTime] = useState(0);
useEffect(() => {
// 启动定时器
const timer = setInterval(() => {
setTime(prev => prev + 1);
}, 1000);
// 清理函数:卸载时清除定时器(避免内存泄漏)
return () => clearInterval(timer);
}, []); // 空依赖,仅挂载执行
return 当前时间:{time}秒;
}
适用场景
数据请求、DOM操作(如设置文档标题、操作DOM元素)、定时器/计时器、事件监听(如window.addEventListener)、订阅(如WebSocket连接)、清理无用资源(避免内存泄漏)。
注意事项
- 清理函数的执行时机:组件卸载时、setup函数再次执行前(依赖变化时),必须清理可能导致内存泄漏的资源(定时器、事件监听、WebSocket、未完成的请求)。
- 依赖数组必须完整:如果setup函数中使用了组件内的变量、状态、函数,必须将其加入依赖数组,否则可能获取到旧值(闭包陷阱);若依赖数组为空,setup函数中不能使用任何组件内的动态值。
- 避免无限循环:如果setup函数中修改了依赖数组中的值,会导致依赖变化,再次触发setup函数,形成无限循环(如
useEffect(() => setCount(prev => prev + 1), [count]))。 - 副作用函数是异步的:不能直接给setup函数加
async(会导致清理函数失效),若需执行异步逻辑,需在setup函数内部定义异步函数并调用。
二、副作用进阶Hooks(18+新增,替代useEffect特定场景)
React 18 新增了两个副作用Hook,专门解决useEffect的部分痛点,针对性更强,在特定场景下更推荐使用。
1. useLayoutEffect ------ 同步DOM副作用
核心作用
与useEffect功能类似,区别在于「执行时机」:useLayoutEffect在DOM更新后、浏览器绘制前同步执行,useEffect在浏览器绘制后异步执行。适合需要同步操作DOM、避免页面闪烁的场景。
语法
scss
useLayoutEffect(setup, dependencies?);
// 语法与useEffect完全一致,仅执行时机不同
实战示例
scss
import { useLayoutEffect, useState, useRef } from 'react';
// 示例:同步调整DOM位置,避免页面闪烁
function LayoutDemo() {
const [width, setWidth] = useState(0);
const ref = useRef(null);
// useLayoutEffect:DOM更新后立即执行,同步获取DOM尺寸并调整
useLayoutEffect(() => {
if (ref.current) {
// 同步获取DOM宽度
setWidth(ref.current.offsetWidth);
// 同步调整DOM位置(避免闪烁)
ref.current.style.marginLeft = `${(window.innerWidth - width) / 2}px`;
}
}, [width]);
return <div ref={0px', background: 'red' }} />;
}
适用场景
需要同步操作DOM、获取DOM尺寸/位置、调整DOM样式(避免页面闪烁)的场景,如弹窗居中、滚动位置调整、DOM尺寸监听。
注意事项
- 执行时机:DOM更新后、浏览器绘制前,同步执行,会阻塞浏览器绘制,若逻辑过久会影响页面性能,慎用。
- 与useEffect的区别:大部分场景用
useEffect即可,只有需要同步操作DOM、避免闪烁时,才用useLayoutEffect。 - 服务器端渲染(SSR):
useLayoutEffect在服务器端不会执行,若需兼容SSR,优先用useEffect,或在客户端判断后执行。
2. useInsertionEffect ------ CSS-in-JS 专用副作用
核心作用
React 18 新增,专门用于 CSS-in-JS 库(如Styled Components、Emotion),在DOM更新前插入CSS样式,避免样式闪烁,是useLayoutEffect的补充。
语法
scss
useInsertionEffect(setup, dependencies?);
// 语法与useEffect、useLayoutEffect一致,执行时机不同
执行时机
DOM更新前执行,早于useLayoutEffect,专门用于插入CSS样式,确保样式在DOM渲染前生效,避免样式闪烁。
适用场景
仅用于 CSS-in-JS 库的样式插入,普通开发场景几乎用不到,日常开发可忽略。
注意事项
- 不能访问DOM:执行时机在DOM更新前,无法获取DOM元素(如
ref.current为null),仅能插入CSS样式。 - 非CSS-in-JS不用:普通CSS、Sass/Less等场景,无需使用,用
useEffect或useLayoutEffect即可。
三、状态进阶Hooks(复杂状态管理)
当状态逻辑复杂(如依赖多个状态、状态更新依赖其他状态)时,使用这类Hook,简化状态管理逻辑。
1. useReducer ------ 复杂状态管理(替代useState)
核心作用
用于管理复杂状态(如状态是对象/数组、状态更新依赖多个因素、多个状态相互关联),通过「 reducer 函数」统一处理状态更新逻辑,类似Redux的核心思想,比useState更适合复杂场景。
语法
csharp
// reducer:状态更新函数,接收当前状态和动作,返回新状态
// initialState:初始状态
// init:可选,初始化函数,用于延迟初始化状态(适合昂贵计算)
const [state, dispatch] = useReducer(reducer, initialState, init?);
// reducer函数格式
function reducer(state, action) {
switch (action.type) {
case 'TYPE1':
return newState1; // 根据action返回新状态
case 'TYPE2':
return newState2;
default:
return state; // 默认返回原状态,避免报错
}
}
实战示例
javascript
import { useReducer, useState } from 'react';
// 1. 定义reducer函数:统一处理状态更新
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
// 添加待办事项,返回新数组(不能修改原数组)
return [...state, { id: Date.now(), text: action.payload, done: false }];
case 'TOGGLE_TODO':
// 切换待办状态
return state.map(todo =>
todo.id === action.payload ? { ...todo, done: !todo.done } : todo
);
case 'DELETE_TODO':
// 删除待办事项
return state.filter(todo => todo.id !== action.payload);
default:
return state;
}
}
// 2. 使用useReducer管理状态
function TodoList() {
// 初始状态:空数组
const [todos, dispatch] = useReducer(todoReducer, []);
const [text, setText] = useState('');
// 触发状态更新:通过dispatch发送action
const addTodo = () => {
if (!text.trim()) return;
dispatch({ type: 'ADD_TODO', payload: text }); // payload:传递参数
setText('');
};
return (
<input value={e) => setText(e.target.value)} />
<button onClick={}>添加待办
{todos.map(todo => (
<li
key={one ? 'line-through' : 'none' }}
onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
>
{todo.text}<button onClick={ dispatch({ type: 'DELETE_TODO', payload: todo.id })}>删除
))}
);
}
适用场景
复杂状态管理:待办列表、购物车、表单多字段状态、多个状态相互关联(如登录状态+用户信息+权限)。
注意事项
- reducer必须是纯函数:不能修改原状态(必须返回新状态),不能包含副作用(如请求、DOM操作),仅处理状态更新逻辑。
- action格式规范:推荐使用
{ type: 'XXX', payload: 数据 }的格式,type用大写字符串,便于维护和调试。 - 与useState的选择:简单状态用
useState,复杂状态(多状态关联、多更新逻辑)用useReducer。 - 延迟初始化:如果初始状态需要昂贵计算,可使用第三个参数
init函数,仅首次渲染执行一次。
四、引用Hook(获取DOM/保存持久值)
用于获取DOM元素、保存组件渲染周期内的持久值(不随渲染变化),核心是useRef,衍生出useImperativeHandle用于暴露组件内部方法。
1. useRef ------ 获取DOM/保存持久值(高频)
核心作用
有两个核心用途:① 获取DOM元素或组件实例;② 保存组件渲染周期内的持久值(不随渲染重新初始化,修改不会触发组件重新渲染)。
语法
csharp
// initialValue:初始值,可选(默认undefined)
const ref = useRef(initialValue);
// 获取DOM:通过ref.current访问DOM元素
// 保存持久值:通过ref.current修改/访问,修改不会触发渲染
实战示例
scss
import { useRef, useState, useEffect } from 'react';
// 示例1:获取DOM元素(如输入框聚焦)
function InputFocus() {
// 创建ref,绑定到输入框
const inputRef = useRef(null);
useEffect(() => {
// 组件挂载时,让输入框自动聚焦(访问ref.current获取DOM)
inputRef.current?.focus();
}, []);
return <input ref={聚焦" />;
}
// 示例2:保存持久值(不触发渲染)
function TimerWithRef() {
const [time, setTime] = useState(0);
// 保存定时器ID,持久化存储,不随渲染变化
const timerRef = useRef(null);
useEffect(() => {
// 启动定时器,将ID存入ref
timerRef.current = setInterval(() => {
setTime(prev => prev + 1);
}, 1000);
// 清理定时器:从ref中获取ID
return () => clearInterval(timerRef.current);
}, []);
// 暂停定时器:修改ref.current,不会触发组件渲染
const pause = () => {
clearInterval(timerRef.current);
};
return (
时间:{time}秒<button onClick={暂停 );
}
适用场景
获取DOM元素(输入框聚焦、获取DOM尺寸)、保存定时器ID、保存WebSocket连接、保存组件渲染周期内的临时值(不触发渲染)。
注意事项
- ref.current修改不触发渲染:与
useState不同,修改ref.current的值不会导致组件重新渲染,适合保存不需要触发渲染的临时值。 - DOM ref的初始化时机:组件挂载完成后,
ref.current才会指向DOM元素,挂载前为null,需用?.避免报错。 - 函数组件不能直接获取实例:如果给函数组件绑定ref,需配合
forwardRef和useImperativeHandle,才能暴露组件内部方法/属性。
2. useImperativeHandle ------ 暴露组件内部方法(进阶)
核心作用
配合forwardRef使用,让父组件通过ref获取子组件的内部方法/属性,自定义暴露给父组件的内容,避免暴露整个子组件实例,提升封装性。
语法
scss
// ref:父组件传递的ref
// createHandle:函数,返回暴露给父组件的方法/属性对象
// dependencies:依赖数组,依赖变化时重新生成暴露的内容
useImperativeHandle(ref, createHandle, dependencies?);
// 必须配合forwardRef包裹子组件,才能接收父组件传递的ref
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
// 暴露给父组件的方法/属性
method1: () => {},
property1: ''
}), []);
return 子组件;
});
实战示例
javascript
import { useRef, forwardRef, useImperativeHandle } from 'react';
// 子组件:配合forwardRef和useImperativeHandle,暴露内部方法
const ChildInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
// 暴露给父组件的方法:聚焦输入框、清空输入框
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => inputRef.current.value = ''
}), []); // 空依赖,暴露的方法不随依赖变化
return <input ref={" />;
});
// 父组件:通过ref调用子组件暴露的方法
function Parent() {
const childRef = useRef(null);
const handleFocus = () => {
// 调用子组件暴露的focus方法
childRef.current?.focus();
};
const handleClear = () => {
// 调用子组件暴露的clear方法
childRef.current?.clear();
};
return (
<ChildInput ref={childRef} />
<button onClick={聚焦输入框<button onClick={清空输入框
);
}
适用场景
父组件需要调用子组件内部方法的场景:输入框聚焦/清空、弹窗显示/隐藏、子组件表单提交等。
注意事项
- 必须配合forwardRef:子组件必须用
forwardRef包裹,才能接收父组件传递的ref,否则无法使用useImperativeHandle。 - 避免过度暴露:仅暴露父组件需要的方法/属性,不要暴露整个子组件的内部状态,保持子组件的封装性。
- 依赖数组:如果暴露的方法依赖子组件的状态/ props,必须将其加入依赖数组,否则可能获取到旧值。
五、上下文Hook(跨组件状态共享)
用于跨组件共享状态(无需逐层传递props),核心是useContext,配合createContext使用,替代类组件的Context API。
1. useContext ------ 跨组件状态共享(高频)
核心作用
接收一个Context对象(由createContext创建),获取Context中共享的状态和方法,实现跨组件(父子、祖孙、兄弟)状态共享,无需逐层传递props(解决props drilling问题)。
语法
ini
// 1. 创建Context对象(可设置默认值,可选)
const MyContext = createContext(defaultValue);
// 2. 用Provider包裹组件树,提供共享状态
<MyContext.Provider value={共享的状态/方法}>
子组件树
</MyContext.Provider>
// 3. 在子组件中使用useContext获取共享内容
const context = useContext(MyContext);
实战示例
ini
import { createContext, useContext, useState } from 'react';
// 1. 创建Context(主题上下文,默认值为light)
const ThemeContext = createContext('light');
// 2. 顶层组件:提供共享状态(主题切换)
function App() {
const [theme, setTheme] = useState('light');
// 切换主题的方法
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
// 用Provider包裹子组件,传递共享的theme和toggleTheme
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<div style={light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff',
minHeight: '100vh'
}}>
<Header />
<Content />
</ThemeContext.Provider>
);
}
// 3. 子组件Header:使用useContext获取共享主题
function Header() {
const { theme } = useContext(ThemeContext);
return 当前主题:{theme};
}
// 4. 子组件Content:使用useContext获取共享方法,切换主题
function Content() {
const { toggleTheme } = useContext(ThemeContext);
return <button onClick={切换主题;
}
适用场景
跨组件状态共享:主题切换、用户登录状态、语言切换、全局配置(如接口基础地址)等。
注意事项
- 必须有Provider包裹:如果子组件使用
useContext,但没有被对应的Context.Provider包裹,会获取到Context的默认值(注意:默认值仅在没有Provider时生效,不是 fallback 值)。 - 状态更新会触发所有使用该Context的组件重新渲染:如果共享状态频繁更新,可能导致性能问题,可配合
useMemo优化。 - Context嵌套:多个Context可以嵌套使用,子组件会获取最近的Provider提供的值。
- TypeScript适配:创建Context时,建议指定类型,避免类型错误(如
createContext<{ theme: string; toggleTheme: () => void } | undefined>(undefined))。
六、性能优化Hooks(避免不必要渲染)
React组件默认会在父组件渲染时重新渲染,这类Hook用于优化性能,避免不必要的渲染,提升组件运行效率。
1. useMemo ------ 缓存计算结果(高频)
核心作用
缓存「昂贵计算的结果」,仅当依赖数组中的值发生变化时,才重新计算结果;否则直接返回缓存的结果,避免每次渲染都重复执行昂贵计算,提升性能。
语法
scss
// compute:需要缓存结果的计算函数(返回计算结果)
// dependencies:依赖数组,仅当依赖变化时,重新执行compute
const memoizedValue = useMemo(compute, dependencies);
实战示例
scss
import { useMemo, useState } from 'react';
// 示例:缓存昂贵计算结果(如数组排序、过滤)
function ExpensiveCompute() {
const [list, setList] = useState([3, 1, 4, 1, 5, 9, 2, 6]);
const [count, setCount] = useState(0);
// 昂贵计算:数组排序(假设数组很大,排序耗时)
// useMemo缓存排序结果,仅当list变化时重新排序
const sortedList = useMemo(() => {
console.log('重新排序'); // 仅list变化时打印
return [...list].sort((a, b) => a - b);
}, [list]); // 依赖list,count变化不重新计算
return (
排序后:{sortedList.join(',')}<button onClick={ setCount(prev => prev + 1)}>计数:{count}<button onClick={ setList([...list, Math.random()])}>添加元素
);
}
适用场景
有昂贵计算的场景:数组排序、过滤、复杂数据处理、大量DOM节点渲染前的计算等。
注意事项
- 不要过度使用:简单计算(如
a + b)无需使用useMemo,缓存本身也有性能开销,仅用于昂贵计算。 - 依赖数组必须完整:compute函数中使用的所有变量、状态、props,都必须加入依赖数组,否则可能返回缓存的旧结果。
- 返回值是缓存的结果:
useMemo返回的是计算结果,不是函数,与useCallback(缓存函数)区分开。
2. useCallback ------ 缓存函数(高频)
核心作用
缓存函数引用,仅当依赖数组中的值发生变化时,才重新创建函数;否则直接返回缓存的函数引用,避免因函数引用变化导致子组件不必要的重新渲染(尤其配合React.memo使用)。
语法
scss
// callback:需要缓存的函数
// dependencies:依赖数组,仅当依赖变化时,重新创建函数
const memoizedCallback = useCallback(callback, dependencies);
实战示例
ini
import { useCallback, useState, memo } from 'react';
// 子组件:用memo包裹,避免不必要的渲染(仅props变化时渲染)
const Child = memo(({ onClick, name }) => {
console.log('子组件渲染'); // 仅onClick或name变化时打印
return <button onClick={{name};
});
// 父组件:用useCallback缓存函数,避免子组件频繁渲染
function Parent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('按钮');
// 缓存onClick函数,仅当name变化时重新创建函数
const handleClick = useCallback(() => {
console.log('点击按钮:', name);
}, [name]); // 依赖name,count变化不重新创建函数
return (
<Child onClick={handleClick} name={name} />
<button onClick={ setCount(prev => prev + 1)}>计数:{count}<button onClick={ setName('新按钮')}>修改按钮名称
);
}
适用场景
父组件向子组件传递函数,且子组件用React.memo包裹的场景:避免因父组件渲染导致函数引用变化,进而触发子组件不必要的渲染。
注意事项
- 配合React.memo使用:
useCallback本身不会阻止父组件渲染,需配合React.memo(包裹子组件),才能避免子组件不必要的渲染。 - 依赖数组必须完整:缓存的函数中使用的所有变量、状态、props,都必须加入依赖数组,否则可能获取到旧值(闭包陷阱)。
- 与useMemo的区别:
useCallback缓存函数引用,useMemo缓存计算结果;useCallback(fn, deps)等价于useMemo(() => fn, deps)。
3. useMemoizedFn ------ 缓存函数(无依赖,18+新增)
核心作用
React 18 新增(需导入react-dom),用于缓存函数引用,无需依赖数组 ,函数内部能获取到最新的组件状态/ props,避免闭包陷阱,比useCallback更简洁。
语法
javascript
import { useMemoizedFn } from 'react-dom';
// callback:需要缓存的函数,无需依赖数组
const memoizedFn = useMemoizedFn(callback);
实战示例
ini
import { useState, memo } from 'react';
import { useMemoizedFn } from 'react-dom';
const Child = memo(({ onClick }) => {
console.log('子组件渲染');
return <button onClick={点击;
});
function Parent() {
const [count, setCount] = useState(0);
// 无需依赖数组,函数内部能获取到最新的count
const handleClick = useMemoizedFn(() => {
console.log('当前计数:', count); // 始终获取最新count
});
return (
<Child onClick={handleClick} />
<button onClick={ setCount(prev => prev + 1)}>计数:{count}
);
}
适用场景
需要缓存函数,且函数内部依赖组件状态/ props,不想手动维护依赖数组的场景(简化useCallback的使用)。
注意事项
- 无需依赖数组:函数内部能自动获取最新的组件状态/ props,无需手动添加依赖,避免遗漏依赖导致的闭包陷阱。
- 需导入react-dom:
useMemoizedFn不是从react导入,而是从react-dom导入,注意导入路径。 - React版本要求:仅支持React 18+,低版本React无法使用。
七、并发模式Hooks(React 18+ 新增,并发渲染)
React 18 引入并发渲染机制,这类Hook用于配合并发模式,控制组件的渲染优先级、中断/恢复渲染,提升用户体验。
1. useTransition ------ 低优先级更新(高频)
核心作用
将某些状态更新标记为「低优先级」,让高优先级更新(如输入框输入、按钮点击)优先执行,避免低优先级更新(如大量数据渲染)阻塞UI,提升用户体验。
语法
scss
// startTransition:函数,包裹低优先级更新逻辑
// isPending:布尔值,标记低优先级更新是否正在进行(可用于显示加载状态)
const [isPending, startTransition] = useTransition();
// 使用:将低优先级更新逻辑放入startTransition
startTransition(() => {
setLowPriorityState(newValue);
});
实战示例
ini
import { useTransition, useState } from 'react';
// 示例:输入框搜索(高优先级)+ 搜索结果渲染(低优先级)
function Search() {
const [searchText, setSearchText] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
// 输入框变化:高优先级更新,优先执行,不阻塞输入
const handleInputChange = (e) => {
const value = e.target.value;
setSearchText(value);
// 搜索结果渲染:低优先级更新,在高优先级更新完成后执行
startTransition(() => {
// 模拟昂贵的搜索逻辑(如过滤大量数据)
const filteredResults = Array(10000).fill(0).filter((_, index) =>
index.toString().includes(value)
);
setResults(filteredResults);
});
};
return (
<input
type="text"
value={
onChange={handleInputChange}
placeholder="搜索"
/>
{/* 低优先级更新正在进行时,显示加载状态 */}
{isPending && 加载中...}
{results.map((item, index) => (
<li key={{index}
))}
);
}
适用场景
高优先级操作与低优先级操作并存的场景:输入框搜索、下拉框筛选、大量数据渲染、路由切换前的准备工作等。
注意事项
- 低优先级更新可被中断:如果有更高优先级的更新(如输入框输入),低优先级更新会被中断,等高优先级更新完成后再恢复,避免阻塞UI。
- 不能用于紧急更新:如表单提交、按钮点击后的状态更新(高优先级),不能用
useTransition,否则会导致更新延迟。 - isPending状态:用于显示低优先级更新的加载状态,提升用户体验,避免用户误以为页面卡死。
2. useDeferredValue ------ 延迟更新值(进阶)
核心作用
与useTransition类似,用于延迟更新某个值,将其标记为低优先级,优先保证高优先级更新的响应速度;区别在于useDeferredValue是"延迟值",useTransition是"延迟更新逻辑"。
语法
scss
// value:需要延迟的原始值
// options:可选配置,如timeoutMs(延迟时间)
const deferredValue = useDeferredValue(value, options?);
实战示例
ini
import { useDeferredValue, useState } from 'react';
// 示例:输入框(高优先级)+ 延迟显示输入值(低优先级)
function DeferredDemo() {
const [inputValue, setInputValue] = useState('');
// 延迟更新inputValue,优先保证输入框响应
const deferredInput = useDeferredValue(inputValue);
return (
<input
type="text"
value={ onChange={(e) => setInputValue(e.target.value)}
placeholder="输入内容"
/>
{/* 延迟显示输入值,避免输入时卡顿 */}
延迟显示:{deferredInput}
);
}
适用场景
需要延迟显示某个值,优先保证高优先级操作的场景:输入框实时显示、大量数据渲染时的延迟更新等,与useTransition互补。
注意事项
- 与useTransition的区别:
useDeferredValue是延迟"值",useTransition是延迟"更新逻辑";useDeferredValue更适合简单的"值延迟"场景,useTransition更适合复杂的"更新逻辑延迟"场景。 - 延迟值是只读的:
deferredValue是延迟后的只读值,不能直接修改,修改需通过原始值(如inputValue)。
八、工具类Hooks(辅助开发,低频但实用)
这类Hooks用于辅助开发,解决特定场景的问题,使用频率较低,但需要时能大幅简化代码。
1. useDebugValue ------ 自定义Hook调试(调试用)
核心作用
用于自定义Hook中,在React开发者工具中显示自定义Hook的调试信息,方便调试,不影响组件功能。
语法
scss
// label:调试信息(字符串或函数,函数返回调试信息)
useDebugValue(label);
// 示例:自定义Hook中使用
function useCustomHook() {
// 自定义Hook逻辑
useDebugValue('自定义Hook调试信息');
return ...;
}
适用场景
开发自定义Hook时,用于调试,方便在React开发者工具中查看自定义Hook的状态和信息。
注意事项
- 仅用于调试:生产环境中,
useDebugValue会被自动移除,不会影响性能。 - 适合复杂自定义Hook:简单自定义Hook无需使用,复杂自定义Hook(多状态、多逻辑)使用,提升调试效率。
2. useId ------ 生成唯一ID(高频)
核心作用
生成跨渲染、跨组件的唯一ID,用于表单标签关联(label的for属性与input的id属性)、 accessibility(无障碍)开发,避免ID重复。
语法
ini
// 生成唯一ID
const id = useId();
// 生成带前缀的唯一ID(多个ID关联时使用)
const id = useId();
const inputId = `${id}-input`;
const labelId = `${id}-label`;
实战示例
javascript
import { useId } from 'react';
// 示例1:表单标签关联,生成唯一ID
function FormInput() {
// 生成唯一ID
const id = useId();
return (
{/* label