在 React 中,Hook 和 Context 是两个核心特性,用于解决函数组件的状态管理、副作用处理和跨组件数据传递问题,是现代 React 开发的基础。下面用通俗的语言 + 核心用法 + 实际场景,帮你彻底搞懂:
一、先搞懂 Hook:函数组件的“能力扩展器”
1. 什么是 Hook?
Hook 是 React 16.8 新增的函数,专门用于在函数组件中实现原本只有类组件才能做到的事情(比如状态管理、生命周期、 Refs 等)。
它的核心目的是:让函数组件摆脱“无状态”的限制,同时避免类组件的复杂度(比如 this 绑定、生命周期拆分混乱)。
关键特点:
- 只能在函数组件或自定义 Hook 中调用(不能在类组件、普通函数里用)
- 必须在组件顶层调用(不能嵌套在
if、for、useEffect里,保证调用顺序稳定)
2. 常用内置 Hook 及用法
React 提供了多个内置 Hook,最常用的有 4 个,覆盖 90% 场景:
(1)useState:管理组件内部状态
- 作用:给函数组件添加“局部状态”(比如输入框值、开关状态、列表数据)
- 语法:
const [状态, 修改状态的函数] = useState(初始值) - 例子:计数器组件
import { useState } from 'react';
function Counter() {
// 声明状态:count(当前值),setCount(修改函数),初始值 0
const [count, setCount] = useState(0);
return (
<div>
计数:{count}
{/* 点击修改状态(注意:不能直接写 count++,要传新值) */}
<button onClick={() => setCount(count + 1)}>加 1</button>
</div>
);
}
(2)useEffect:处理副作用
- 作用:处理组件的“副作用”(比如网络请求、DOM 操作、定时器、订阅),替代类组件的
componentDidMount、componentDidUpdate、componentWillUnmount - 语法:
useEffect(() => { 副作用逻辑 }, [依赖数组]) - 核心规则:
- 依赖数组为空
[]:只在组件首次渲染时执行(类似componentDidMount) - 依赖数组有值
[a, b]:组件首次渲染 +a或b变化时执行(类似componentDidUpdate) - 无依赖数组:每次组件渲染都执行
- 返回清理函数:在组件卸载或下次副作用执行前触发(类似
componentWillUnmount)
- 依赖数组为空
- 例子:组件挂载时请求数据,卸载时清除定时器
import { useState, useEffect } from 'react';
function User() {
const [user, setUser] = useState(null);
useEffect(() => {
// 副作用:请求用户数据
const fetchUser = async () => {
const res = await fetch('/api/user');
setUser(await res.json());
};
fetchUser();
// 清理函数:组件卸载时清除定时器(避免内存泄漏)
const timer = setInterval(() => console.log('计时'), 1000);
return () => clearInterval(timer);
}, []); // 空依赖:只执行一次
return <div>{user ? user.name : '加载中...'}</div>;
}
(3)useContext:读取 Context 数据
- 作用:在函数组件中快速读取 Context 中的数据(后面讲 Context 时会结合用)
- 语法:
const 数据 = useContext(Context 对象)
(4)useRef:获取 DOM 元素或保存可变值
- 作用:两种核心用法:
- 获取 DOM 元素(类似类组件的
createRef) - 保存一个“不会触发组件重新渲染”的可变值(比如定时器 ID、前一次的状态)
- 获取 DOM 元素(类似类组件的
- 例子:获取输入框 DOM
import { useRef } from 'react';
function Input() {
// 创建 ref 对象
const inputRef = useRef(null);
const focusInput = () => {
// 通过 ref.current 获取 DOM 元素
inputRef.current.focus();
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>聚焦输入框</button>
</>
);
}
3. 自定义 Hook
如果多个组件有重复逻辑(比如“请求数据+加载状态+错误处理”),可以把逻辑抽成自定义 Hook(命名必须以 use 开头),实现复用。
例子:自定义 useFetch Hook 封装请求逻辑
import { useState, useEffect } from 'react';
// 自定义 Hook:封装请求逻辑
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url);
if (!res.ok) throw new Error('请求失败');
setData(await res.json());
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
// 返回给组件使用的数据和状态
return { data, loading, error };
}
// 组件中使用自定义 Hook
function UserList() {
const { data: users, loading, error } = useFetch('/api/users');
if (loading) return <div>加载中...</div>;
if (error) return <div>错误:{error}</div>;
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
二、再搞懂 Context:跨组件数据“全局通道”
1. 什么是 Context?
Context 是 React 提供的跨组件数据传递机制,用于解决“props drilling(属性透传)”问题。
比如:爷爷组件有一个“主题色”数据,需要传给孙子、曾孙子组件,如果用 props 传递,中间的父组件必须层层接收再传递(即使父组件自己不用这个数据),非常繁琐。
Context 的核心目的:让数据在组件树中“全局可用”,无需层层透传 props。
2. Context 的核心用法(3 步走)
Context 的使用分 3 步:创建 Context → 提供数据 → 消费数据
步骤 1:创建 Context(单独文件,方便复用)
// themes.js
import { createContext } from 'react';
// 创建 Context,可选:传入默认值(只有当没有 Provider 时才会生效)
const ThemeContext = createContext('light'); // 默认主题:浅色
export default ThemeContext;
步骤 2:提供数据(在组件树顶层用 Provider 包裹)
用 ThemeContext.Provider 组件包裹需要使用该数据的组件树,通过 value 属性传入要共享的数据。
// App.js
import ThemeContext from './themes';
import Header from './Header';
import Content from './Content';
function App() {
const theme = 'dark'; // 要共享的主题数据(可以是状态、对象等)
// Provider 包裹子组件,所有子组件都能访问 value 中的数据
return (
<ThemeContext.Provider value={theme}>
<Header /> {/* 父组件(不用主题数据) */}
<Content /> {/* 子组件(可能用主题数据) */}
</ThemeContext.Provider>
);
}
步骤 3:消费数据(子组件中读取 Context)
有两种方式读取 Context,推荐用 useContext(更简洁,配合 Hook 使用):
方式 1:useContext(函数组件推荐)
// Button.js(孙子组件)
import { useContext } from 'react';
import ThemeContext from './themes';
function Button() {
// 直接读取 Context 数据
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
主题按钮
</button>
);
}
方式 2:Context.Consumer(类组件/函数组件都能用)
// Button.js
import ThemeContext from './themes';
function Button() {
return (
<ThemeContext.Consumer>
{/* 必须传入一个函数,参数是 Context 数据 */}
{theme => (
<button style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
主题按钮
</button>
)}
</ThemeContext.Consumer>
);
}
3. Context 的常见场景
- 共享全局配置(比如主题色、语言、权限状态)
- 共享用户信息(比如登录状态、用户名)
- 共享工具函数(比如全局通知、日志函数)
注意:Context 不是“全局状态管理工具”!如果需要共享“会频繁修改且多个组件依赖”的数据(比如购物车、表单状态),单独用 Context 会导致不必要的重渲染,建议配合
useReducer或 Redux、Zustand 等状态管理库使用。
三、Hook 和 Context 的关系
两者是互补关系,经常一起使用:
- Context 负责“跨组件传递数据”
- Hook(尤其是
useContext)负责“在函数组件中便捷地读取 Context 数据”
比如上面的主题例子,ThemeContext 传递数据,useContext(ThemeContext) 读取数据,缺一不可。
再比如“全局登录状态”:
- 用
useState管理登录状态(isLogin、userInfo) - 用 Context 把登录状态和修改状态的函数(
login、logout)传递给所有组件 - 子组件用
useContext读取登录状态,或调用login/logout函数
总结
| 特性 | 核心作用 | 关键要点 |
|---|---|---|
| Hook | 给函数组件扩展状态、副作用等能力 | 只能在函数组件/自定义 Hook 顶层调用;命名规范(useXXX) |
| Context | 跨组件传递数据,解决 props 透传 | 配合 useContext 使用更简洁;不适合频繁修改的复杂状态 |
简单记:
- 组件内部需要状态/副作用 → 用 Hook(
useState/useEffect) - 组件之间需要共享数据 → 用 Context +
useContext