React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性;
Hooks 允许我们在函数组件中管理状态、副作用、上下文等,消除了使用类组件的复杂性;
常用的 React Hooks
useState
-
useState 是 React 提供的最基本的 Hook,用于在函数组件中添加状态管理;它返回一个状态变量和一个更新状态的函数;
-
使用场景
- 适合管理简单的状态;
- 适合管理组件内部的局部状态;
-
示例:
import { createRoot } from "react-dom/client"; import { useState } from 'react'; function Counter() { // 声明一个 state 变量 count 和更新函数 setCount const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> {/* 更新函数可以接受函数参数来基于先前的 state 计算新 state */} <button onClick={() => setCount(prevCount => prevCount + 1)}>Click me</button> </div> ); } createRoot(document.getElementById("root")).render(<Counter />);
useReducer
-
useReducer 是 useState 的替代方案,适合用于管理更复杂的状态逻辑;它通过 reducer 函数来管理状态,类似于 Redux;
-
如果组件内部状态足够多,那么状态会逐渐趋于复杂,这时,需要更好的编程范式来解决状态存储与更新;react 单向数据流告诉了我们,状态的管理需要注意以下几点:
- 使用一个对象存储变量 state;
- 订阅模式实现对于该对象的变更响应处理 reducer;
- 定义更改对象变更的动作 action;
- 订阅该对象的变更,完成状态到视图的映射 ui = fx(state);
-
用一句话来概括:状态由 useReducer 借助 reducer 生发,状态的变更由 dispach 发起,最终状态变更驱动视图更新;
-
使用场景:
- 适合管理复杂的状态逻辑;
- 状态更新依赖于先前状态;
-
示例代码
import { createRoot } from "react-dom/client"; import { useReducer } from 'react'; // 定义 reducer 函数 function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } } function Counter() { // 使用 useReducer const [state, dispatch] = useReducer(reducer, { count: 0 }); return ( <> Count: {state.count} <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> </> ); } createRoot(document.getElementById("root")).render(<Counter />);
useContext
-
useContext 用于共享全局状态,避免了通过多层组件传递 props;它通过 Context 对象提供全局状态管理;2. 使用场景:
- 适合管理跨组件树的全局状态;
- 避免多层组件传递 props;
-
示例:
import { createRoot } from "react-dom/client"; import { createContext, useContext } from 'react'; // 1. 创建 Context const ThemeContext = createContext('light'); function App() { // 2. 提供 Context 值 return ( <ThemeContext.Provider value="light"> <Toolbar /> </ThemeContext.Provider> ); }; function Toolbar() { return <ThemedButton />; }; function ThemedButton() { // 3. 使用 Context const theme = useContext(ThemeContext); return <button style={{ background: theme === 'dark' ? '#333' : '#eee' }}>Themed Button</button>; }; createRoot(document.getElementById("root")).render(<App />);
useEffect
-
useEffect 用于在函数组件中执行副作用操作,例如数据获取、订阅或手动更改 DOM,useEffect 会在组件渲染后执行;
-
用法:
import { createRoot } from "react-dom/client"; import { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // 相当于 componentDidMount 和 componentDidUpdate useEffect(() => { // 使用浏览器 API 更新文档标题 document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } createRoot(document.getElementById("root")).render(<Example />);
-
依赖数组控制执行时机
// 只在组件挂载时执行(相当于 componentDidMount) useEffect(() => { console.log('Component mounted'); }, []); // 只在 count 变化时执行 useEffect(() => { console.log('Count changed:', count); }, [count]);
-
清理副作用
useEffect(() => { const timer = setInterval(() => { console.log('Timer tick'); }, 1000); return () => { // 相当于 componentWillUnmount clearInterval(timer); }; }, []);
useRef
-
useRef 返回一个可变的 ref 对象,该对象的 .current 属性被初始化为传入的参数 (initialValue);useRef 常用于访问 DOM 元素直接或存储某个可变值,该值在组件的整个生命周期内保持不变;
-
用法:
JSXJSXimport { createRoot } from "react-dom/client"; import { useRef, useEffect } from 'react'; function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { // `current` 指向已挂载的 input 元素 inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); } createRoot(document.getElementById("root")).render(<TextInputWithFocusButton />);
import { createRoot } from "react-dom/client"; import { useRef, useEffect } from 'react'; // 保存可变值(不会触发重新渲染) function TimerComponent() { const intervalRef = useRef(); useEffect(() => { intervalRef.current = setInterval(() => { console.log('Timer tick'); }, 1000); return () => { clearInterval(intervalRef.current); }; }, []); return <div>Check console for timer logs</div>; } createRoot(document.getElementById("root")).render(<TimerComponent />);
useMemo
-
useMemo 用于缓存计算结果,避免在每次渲染时都进行昂贵的计算;
-
昂贵的计算:
function ExpensiveComponent({ list }) { // 只有 list 变化时才重新计算排序结果,list 不变重新渲染的时候也不会重新计算 const sortedList = useMemo(() => { console.log('Sorting...'); return [...list].sort((a, b) => a.value - b.value); }, [list]); return <div>{sortedList.map(item => <div key={item.id}>{item.value}</div>)}</div>; }
-
避免不必要的重新渲染:
function Parent({ a }) { // 只有 a 变化时才重新创建对象,a不变重新渲染的时候也不会重新计算 const childProps = useMemo(() => ({ value: a }), [a]); return <Child data={childProps} />; } // Child 组件使用 React.memo 避免不必要的渲染 const Child = React.memo(function Child({ data }) { console.log('Child rendered'); return <div>{data.value}</div>; });
-
引用类型的依赖项:
function Component({ items }) { // 避免每次渲染都创建新数组 const itemIds = useMemo(() => items.map(item => item.id), [items]); useEffect(() => { console.log('Item IDs changed', itemIds); }, [itemIds]); // 只有当 items 实际变化时才会触发 }
useCallback
-
useCallback 用于缓存函数引用,避免在每次渲染时都创建新的函数;
-
优化子组件渲染(配合 React.memo):
function Parent() { const [count, setCount] = useState(0); // 缓存回调函数,避免每次渲染都创建新函数 const increment = useCallback(() => { setCount(c => c + 1); }, []); // 依赖数组为空,函数永远不会改变 return ( <div> <Child onClick={increment} /> <div>Count: {count}</div> </div> ); } // 使用 React.memo 避免不必要的子组件渲染 const Child = React.memo(function Child({ onClick }) { console.log('Child rendered'); return <button onClick={onClick}>Increment</button>; });
-
作为其他 Hook 的依赖项:
function Example() { const [data, setData] = useState(null); // 缓存 fetch 函数 const fetchData = useCallback(async () => { const response = await fetch('/api/data'); setData(await response.json()); }, []); // 依赖数组为空,函数永远不会改变 // 只有当 fetchData 变化时才重新创建 effect useEffect(() => { fetchData(); }, [fetchData]); return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>; }
-
事件处理函数需要依赖当前状态:
function Timer() { const [count, setCount] = useState(0); // 使用 useCallback 确保回调函数能访问到最新的 count const startTimer = useCallback(() => { const timer = setInterval(() => { setCount(c => c + 1); // 使用函数式更新 }, 1000); return () => clearInterval(timer); }, []); useEffect(() => { const cleanup = startTimer(); return cleanup; }, [startTimer]); return <div>Count: {count}</div>; }
-
不适用场景:
- 函数不需要作为 props 传递给子组件
- 函数不用于依赖数组
- 函数创建开销很小
useLayoutEffect
-
useLayoutEffect 与 useEffect 类似,但它在 DOM 更新后、浏览器绘制前同步执行,适用于需要直接操作 DOM 或测量 DOM 元素的场景;
-
谨慎使用:过度使用可能导致性能问题,因为它会阻塞渲染;
-
用法:
import { createRoot } from "react-dom/client"; import { useLayoutEffect, useRef } from 'react'; function LayoutEffectExample() { const ref = useRef(null); useLayoutEffect(() => { console.log('useLayoutEffect'); ref.current.style.backgroundColor = 'yellow'; }); return <div ref={ref}>Layout Effect</div>; } createRoot(document.getElementById("root")).render(<LayoutEffectExample />);
-
与 useEffect 的区别:
特性 useLayoutEffect useEffect 执行时机 DOM 更新后,绘制前 绘制后 阻塞性 同步执行,阻塞绘制 异步执行,不阻塞绘制 适用场景 DOM 测量/操作 数据获取/订阅 SSR 警告 有 无
useImperativeHandle
-
useImperativeHandle 用于自定义通过 ref 暴露给父组件的实例值,通常与 forwardRef 配合使用;
-
用法:
import { createRoot } from "react-dom/client"; import { useRef, forwardRef, useImperativeHandle } from 'react'; const FancyInput = forwardRef((props, ref) => { const inputRef = useRef(); // 控制子组件向父组件暴露哪些方法和属性 useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} />; }); function App() { const inputRef = useRef(); return ( <div> <FancyInput ref={inputRef} /> <button onClick={() => inputRef.current.focus()}>Focus the input</button> </div> ); } createRoot(document.getElementById("root")).render(<App />);
useId
-
useId 是一个用于生成唯一 ID 的 Hook,通常用于无障碍特性 (例如绑定 label 和 input);
-
用法:
import { createRoot } from "react-dom/client"; import React, { useId } from 'react'; function Form() { const id = useId(); return ( <div> <label htmlFor={id}>Name</label> <input id={id} type="text" /> </div> ); } createRoot(document.getElementById("root")).render(<Form />);
useTransition
-
useTransition 是一个用于处理 UI 过渡的 Hook,允许你将某些更新标记为过渡,从而避免阻塞界面;
-
用法:
import { createRoot } from "react-dom/client"; import { useState, useTransition } from 'react'; function App() { const [isPending, startTransition] = useTransition(); const [count, setCount] = useState(0); const handleClick = () => { startTransition(() => { setCount(c => c + 1); }); }; return ( <div> <button onClick={handleClick}>Increment</button> {isPending ? 'Loading...' : <p>Count: {count}</p>} </div> ); } createRoot(document.getElementById("root")).render(<App />);
useDeferredValue
-
useDeferredValue 是 React 19 中用于性能优化的重要 Hook,它允许你延迟更新某些非关键值,从而保持界面的响应性,特别适用于处理高开销的渲染操作;
-
用法:
import { createRoot } from "react-dom/client"; import { useState, useDeferredValue } from 'react'; function App() { const [text, setText] = useState(''); const deferredText = useDeferredValue(text); const handleChange = (e) => { setText(e.target.value); }; return ( <div> <input type="text" value={text} onChange={handleChange} /> <p>{deferredText}</p> </div> ); } createRoot(document.getElementById("root")).render(<App />);
-
典型使用场景:
- 搜索/筛选大型列表
- 图表数据渲染
useSyncExternalStore
-
useSyncExternalStore 是 React 19 中用于安全地订阅外部数据源的 Hook,它专门设计用来解决外部状态管理与 React 集成时的常见问题;
-
基本语法
const state = useSyncExternalStore( subscribe, // 接收一个回调函数,并返回取消订阅的函数 getSnapshot, // 返回当前存储的快照值 getServerSnapshot? // 服务端渲染时使用的快照 );
-
典型使用场景
- 集成 Redux 类状态库
function useReduxStore(store) { return useSyncExternalStore( store.subscribe, () => store.getState() ); }
- 自定义全局状态管理
import { createRoot } from "react-dom/client"; import { useSyncExternalStore } from 'react'; let globalState = { count: 0 }; const listeners = new Set(); function increment() { globalState = { ...globalState, count: globalState.count + 1 }; listeners.forEach(l => l()); } function App() { const state = useSyncExternalStore( (callback) => { listeners.add(callback); return () => listeners.delete(callback); }, () => globalState ); return <button onClick={increment}>{state.count}</button>; } createRoot(document.getElementById("root")).render(<App />);
- 浏览器 API 订阅
function useOnlineStatus() { return useSyncExternalStore( callback => { window.addEventListener('online', callback); window.addEventListener('offline', callback); return () => { window.removeEventListener('online', callback); window.removeEventListener('offline', callback); }; }, () => navigator.onLine ); }
- 集成 Redux 类状态库
-
与其它状态管理方式的对比
特性 useSyncExternalStore useState/useReducer useContext 适用场景 外部状态源 组件内部状态 跨组件共享 性能优化 ✅ 内置优化 ⚠️ 需手动优化 ⚠️ 需注意 并发模式支持 ✅ 完全支持 ✅ 支持 ✅ 支持 SSR支持 ✅ 完整支持 ✅ 支持 ✅ 支持
封装自定义 Hook
-
封装一个用于管理表单输入状态的自定义 Hook
import { useState } from "react"; export function useFormInput(initialValue) { const [value, setValue] = useState(initialValue); const handleChange = (e) => { setValue(e.target.value); }; return { value, onChange: handleChange, }; }
-
使用自定义 Hook
import { createRoot } from "react-dom/client"; import { useFormInput } from './useFormInput'; function Form() { const name = useFormInput(''); const email = useFormInput(''); const handleSubmit = (e) => { e.preventDefault(); alert(`Name: ${name.value}, Email: ${email.value}`); }; return ( <form onSubmit={handleSubmit}> <div> <label> Name: <input type="text" {...name} /> </label> </div> <div> <label> Email: <input type="email" {...email} /> </label> </div> <button type="submit">Submit</button> </form> ); } createRoot(document.getElementById("root")).render(<Form />);
深入理解 Hooks 原理
Hooks 的设计哲学
React Hooks 的核心思想是让函数组件拥有类组件的能力,同时解决类组件的几个关键问题:
- 状态逻辑复用困难 (render props/HOC 带来的嵌套地狱)
- 复杂组件难以理解 (生命周期方法中混杂不相关逻辑)
- 类组件的心智负担 (this 绑定、方法属性等)
Hooks 的实现基础
-
React 内部使用闭包+链表的方式存储 Hooks 状态:
// 简化的内部实现示意 let currentHook = null; // 当前处理的Hook let workInProgressHook = null; // 正在工作的Hook let isMount = true; // 是否是首次渲染 function mountWorkInProgressHook() { const hook = { memoizedState: null, // 存储状态 next: null, // 下一个Hook }; if (!workInProgressHook) { // 第一个Hook workInProgressHook = hook; } else { // 添加到链表末尾 workInProgressHook.next = hook; } return hook; }
-
Hooks 调用规则的本质:“只在最顶层使用 Hooks” 的规则源于 Hooks 的链表存储方式;
// 错误示例 - 条件语句会导致 Hook 顺序变化 if (condition) { useState(1); // 第一次渲染可能调用 } useState(2); // 第二次渲染可能变成第一个 Hook
Hooks 底层架构
React Hooks 的实现依赖于几个关键部分:
- Dispatcher:负责分配当前使用的 Hooks 实现;
- 渲染阶段使用 HooksDispatcherOnMount;
- 更新阶段使用 HooksDispatcherOnUpdate;
- Fiber 架构:每个组件对应一个 Fiber 节点,存储 Hooks 链表;
- 调度系统:协调 Hooks 的更新与渲染时机;
Hooks VS 类组件生命周期
类组件生命周期 | Hooks 等效实现 |
---|---|
componentDidMount | useEffect(fn, []) |
componentDidUpdate | useEffect(fn, [deps]) |
componentWillUnmount | useEffect(() => fn, []) |
shouldComponentUpdate | React.memo / useMemo |
面试题
react 中 useState 返回的更新函数,接受两种形式的参数有什么区别?
-
直接传递新值(如字符串变更)
- 特点:
- 适用于不依赖前一个状态的情况,比如直接替换当前状态;
- 如果多次调用,React 可能会批量处理更新,导致最终结果不符合预期(特别是在异步环境中);
- 示例问题:
const handleClick = () => { setName(name + "A"); // 假设 name 当前是 "Alice" setName(name + "B"); // 这里仍然基于旧的 name ("Alice"),而不是 "AliceA" // 最终 name 可能是 "AliceB" 而不是 "AliceAB" };
- 特点:
-
传递函数(函数变更)
- 特点:
- 适用于依赖前一个状态的情况,确保获取最新的状态值;
- 即使多次调用,也能正确基于前一个更新计算新状态(React 会确保按顺序执行);
- 在异步操作 (如 setTimeout、Promise) 中更安全,避免闭包问题;
- 正确示例:
const handleClick = () => { setName(prev => prev + "A"); // 基于前一个状态 setName(prev => prev + "B"); // 基于前一个更新后的状态 // 最终 name 会是 "AliceAB" };
- 特点:
-
关键区别总结
场景 直接传递新值(如字符串) 传递函数(函数变更) 依赖前一个状态 ❌ 可能出错(闭包问题) ✅ 安全可靠 多次调用时的行为 可能覆盖更新 按顺序执行更新 异步环境 可能出错 安全 适用场景 简单状态更新 复杂或依赖前状态的计算 -
何时使用哪种方式?
- 直接传递值:当新状态不依赖旧状态时 (如直接替换字符串、布尔值切换);
- 传递函数:当新状态依赖旧状态时 (如计数器、字符串拼接、数组/对象更新);
-
额外说明:React 的批处理 (Batching)
- React 会批量处理多个 setState 调用以提高性能;
- 在 React 18 的并发模式下,批处理更加普遍,因此使用函数式更新能避免潜在的问题;
Redux✍️ Redux 初见
上一篇