一、什么是 React Hook?
React Hook,本质上是一套 让函数组件拥有更多能力的机制。
你可以先记住一句最核心的话:
Hook 的作用,就是让函数组件也能拥有状态、生命周期、副作用处理、逻辑复用等能力。
通俗一点说:
以前 React 的函数组件,只会干一件事:
根据数据,把页面渲染出来。
但是现实开发中,一个组件往往不只是"显示页面"这么简单,它还需要做很多事情,比如:
- 记住用户输入的内容
- 控制弹窗开关
- 发送接口请求
- 监听页面变化
- 获取 DOM 元素
- 复用一段公共逻辑
而 Hook,就是 React 提供给函数组件的一套"能力插件"。
你可以把它理解成:
Hook = 给函数组件装功能的工具箱。
二、为什么会有 Hook?
要理解 Hook,先得知道 React 以前是怎么写的。
在 Hook 出现之前,React 中如果你想让组件拥有状态、生命周期这些能力,通常要使用 类组件(Class Component) 。
比如一个最简单的计数器,早期可能要这样写:
scala
import React, { Component } from "react";
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
点击了 {this.state.count} 次
</button>
);
}
}
这段代码没有错,但很多初学者会觉得有点麻烦:
- 要写
class - 要写
constructor - 要写
super - 要写
this.state - 要写
this.setState this指向有时候还容易出问题
随着项目越来越复杂,类组件还会涉及各种生命周期函数,比如:
componentDidMountcomponentDidUpdatecomponentWillUnmount
代码会越来越分散,逻辑也越来越不容易维护。
于是 React 官方推出了 Hook。
它的目标非常明确:
让函数组件也能完成类组件的大部分能力,而且写起来更简洁、更清晰、更容易复用逻辑。
三、你可以怎么理解 Hook?
你可以把一个 React 组件想象成一个员工。
一开始,这个员工只会一件事:
把页面画出来。
比如:
javascript
function Hello() {
return <h1>Hello React</h1>;
}
这就是一个最普通的函数组件。
但如果你想让这个员工更能干一点,比如:
- 记住一个数字
- 页面加载后发请求
- 监听窗口变化
- 找到某个输入框并让它自动获取焦点
那就需要给他配工具。
而 Hook,就是这些工具。
比如:
useState:给员工一个记事本,让他能记住东西useEffect:给员工一个任务清单,让他在页面渲染后做额外事情useRef:给员工一个抽屉,可以放东西,也能找到页面里的某个元素
所以,Hook 并不神秘。
它就是:
让函数组件从"只能展示页面",升级成"能真正干活的组件"。
四、最常见的 Hook:useState
在 React 中,最常用的 Hook 之一,就是 useState。
它的作用非常简单:
让组件记住一个值。
比如,我们写一个最基础的计数器:
javascript
import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
点击了 {count} 次
</button>
);
}
export default Counter;
这段代码怎么理解?
最关键的是这句:
scss
const [count, setCount] = useState(0);
你可以把它翻译成一句人话:
React,帮我准备一个状态,它的初始值是 0,当前值叫 count,修改它的方法叫 setCount。
于是:
count是当前的状态值setCount是修改状态的方法useState(0)里的0是初始值
点击按钮后,执行:
scss
setCount(count + 1);
React 就会更新状态,然后重新渲染页面。
为什么不能直接用普通变量?
很多初学者可能会写出这样的代码:
javascript
function Counter() {
let count = 0;
return (
<button onClick={() => count++}>
点击了 {count} 次
</button>
);
}
看起来你在修改 count,但页面并不会按预期更新。
原因很简单:
普通变量的变化,React 感知不到。
React 只会对"状态"的变化做出响应,而 useState 正是告诉 React:
这个值是组件状态,请你帮我管理它。
所以你可以这样记:
普通变量是你自己偷偷记,React 不知道;useState 是你正式告诉 React,这个值要参与页面更新。
五、第二个非常重要的 Hook:useEffect
除了状态,组件还经常需要做一些"额外的事情"。
比如:
- 页面加载后请求接口
- 设置定时器
- 监听滚动事件
- 修改页面标题
- 组件销毁时做清理
这些事情并不是"渲染 UI"本身,而是渲染之外的行为。
React 把这类操作叫做 副作用(Effect) 。
听起来有点专业,其实你完全可以把它理解成:
组件渲染完后,顺手做的事。
这时候就要用到 useEffect。
先看一个例子:
javascript
import React, { useEffect } from "react";
function Demo() {
useEffect(() => {
console.log("组件渲染完成了");
}, []);
return <div>你好,React Hook</div>;
}
export default Demo;
这段代码的意思就是:
页面渲染出来以后,执行一次里面的代码。
[] 是什么意思?
很多人第一次学 useEffect,最容易懵的地方就是第二个参数。
scss
useEffect(() => {
console.log("执行了");
}, []);
这里的 [] 叫做 依赖数组。
它决定这个副作用什么时候执行。
1. 传空数组 []
scss
useEffect(() => {
console.log("只执行一次");
}, []);
表示:
组件第一次渲染完成后执行一次,以后不再执行。
这很像类组件里的 componentDidMount。
2. 不传第二个参数
javascript
useEffect(() => {
console.log("每次渲染都执行");
});
表示:
组件每次渲染后都会执行。
3. 传入依赖项
scss
useEffect(() => {
console.log("count 变化了");
}, [count]);
表示:
首次渲染会执行,以后只有 count 变化时才执行。
这个机制非常重要,写业务代码时经常会用到。
六、useEffect 最经典的应用:请求接口
比如页面加载后获取用户列表:
javascript
import React, { useEffect, useState } from "react";
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((data) => {
setUsers(data);
});
}, []);
return (
<div>
<h2>用户列表</h2>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
export default UserList;
逻辑很清晰:
- 用
useState([])准备一个用户列表状态 - 用
useEffect在组件第一次渲染后发请求 - 请求回来后通过
setUsers(data)更新状态 - 页面自动重新渲染,显示数据
这就是 Hook 配合使用的典型场景。
七、第三个常见 Hook:useRef
接下来再说一个很常用的 Hook:useRef。
它主要有两个用途:
- 获取 DOM 元素
- 保存一个不会触发页面重新渲染的值
先看第一个用途:获取 DOM。
比如页面加载后让输入框自动聚焦:
javascript
import React, { useEffect, useRef } from "react";
function InputFocus() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} placeholder="请输入内容" />;
}
export default InputFocus;
这里的逻辑是:
useRef(null)创建一个引用对象ref={inputRef}把这个引用绑定到 input 上- 页面渲染后,通过
inputRef.current拿到真实 DOM - 调用
focus()让输入框自动获取焦点
你可以把 useRef 理解成:
给页面元素贴了个标签,方便以后找到它。
useRef 的第二个用途
useRef 还可以用来保存一些值,而且这些值变化时不会导致页面重新渲染。
比如保存一个定时器 id:
javascript
import React, { useEffect, useRef } from "react";
function TimerDemo() {
const timerRef = useRef(null);
useEffect(() => {
timerRef.current = setInterval(() => {
console.log("定时器执行中");
}, 1000);
return () => {
clearInterval(timerRef.current);
};
}, []);
return <div>定时器示例</div>;
}
export default TimerDemo;
这里的 timerRef.current 就像一个小盒子,可以存放数据。
和 useState 的区别是:
useState变化会触发重新渲染useRef变化不会触发重新渲染
所以如果你只是想保存一个值,但这个值不需要展示到页面上,useRef 很合适。
八、React Hook 到底解决了什么问题?
这是很多面试中也会问到的问题。
React Hook 主要解决了三个问题。
1. 让函数组件拥有状态和副作用能力
以前函数组件只能负责展示 UI,复杂逻辑很多都要写在类组件里。
Hook 出现后,函数组件也能做这些事了,开发体验更统一。
2. 逻辑复用更方便
以前如果你想复用一段组件逻辑,常见方法有:
- mixin
- 高阶组件(HOC)
- Render Props
这些方案不是不能用,但随着项目变复杂,代码嵌套会越来越深,理解成本也越来越高。
而 Hook 可以把一段逻辑直接提取成一个自定义 Hook。
比如获取窗口宽度:
javascript
import React, { useEffect, useState } from "react";
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => {
setWidth(window.innerWidth);
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return width;
}
function Page() {
const width = useWindowWidth();
return <div>当前窗口宽度:{width}</div>;
}
export default Page;
这里 useWindowWidth() 就是一个自定义 Hook。
你会发现,这种写法真的很舒服:
- 逻辑抽离清晰
- 复用方便
- 代码结构更自然
3. 让组件代码更简洁、更易维护
类组件中,一个功能的相关代码可能分散在多个生命周期里。
而用 Hook,可以把"相关逻辑"写在一起。
比如接口请求、事件绑定、清理逻辑,都可以围绕一个功能集中组织,这对维护大型项目非常友好。
九、为什么 Hook 都要以 use 开头?
你肯定发现了,React 官方提供的 Hook 都叫:
useStateuseEffectuseRefuseMemouseCallbackuseContext
这是 React 的约定。
凡是 Hook,名字都要以 use 开头。
包括你自己写的自定义 Hook,也最好遵守这个规则:
csharp
function useWindowWidth() {
// ...
}
为什么这么要求?
因为 React 和 ESLint 插件会根据 use 开头来识别:
这是不是一个 Hook。
这样可以更好地检查代码是否符合 Hook 的使用规则。
十、React Hook 的使用规则
Hook 虽然很好用,但它有两条非常重要的规则。
1. 只能在函数组件或自定义 Hook 中调用
你不能在普通的 JavaScript 函数中乱用 Hook。
错误示例:
scss
function test() {
const [count, setCount] = useState(0);
}
这是不允许的。
正确示例:
scss
function Counter() {
const [count, setCount] = useState(0);
}
或者:
scss
function useCounter() {
const [count, setCount] = useState(0);
return { count, setCount };
}
2. 不要在条件语句、循环、嵌套函数中调用 Hook
错误示例:
javascript
function Demo({ flag }) {
if (flag) {
const [count, setCount] = useState(0);
}
return <div>Demo</div>;
}
为什么不行?
因为 React 是按照 Hook 的调用顺序来管理状态的。
如果你把 Hook 写在 if 里面,那么某次渲染执行了,某次渲染又没执行,顺序就乱了,React 就无法正确知道:
哪个 useState 对应哪个状态。
所以一定要记住:
Hook 要写在组件顶层,不能乱嵌套。
十一、再来理解一下 Hook 的本质
很多同学学 Hook 时,最容易卡住的一点是:
函数组件不是每次渲染都会重新执行吗?那它是怎么"记住状态"的?
这个问题问得特别好。
确实,函数组件每次渲染都会重新执行一遍。
但是 React 内部会帮你"记住"每个 Hook 对应的数据。
比如:
javascript
function Demo() {
const [count, setCount] = useState(0);
const [name, setName] = useState("Tom");
return <div>{count} - {name}</div>;
}
React 内部会根据 Hook 的调用顺序,记录:
- 第一个
useState对应count - 第二个
useState对应name
下次渲染时,再按照同样顺序把数据取出来。
这就是为什么 Hook 必须按固定顺序调用。
所以从本质上说:
Hook 是 React 在函数组件中"挂接状态和副作用管理能力"的一种机制。
十二、常见 Hook 快速总结
下面把几个常见 Hook 用最简单的话概括一下。
1. useState
作用:管理状态
适合场景:
- 计数器
- 输入框内容
- 弹窗开关
- 列表数据
- 当前页码
示例:
scss
const [visible, setVisible] = useState(false);
2. useEffect
作用:处理副作用
适合场景:
- 请求接口
- 事件监听
- 定时器
- 手动操作 DOM
- 修改页面标题
示例:
ini
useEffect(() => {
document.title = "首页";
}, []);
3. useRef
作用:获取 DOM 或保存不会引起重渲染的值
适合场景:
- input 自动聚焦
- 获取滚动容器
- 保存定时器 id
- 保存上一次的值
示例:
ini
const inputRef = useRef(null);
4. useMemo
作用:缓存计算结果
适合场景:
- 复杂计算
- 避免重复计算
- 优化性能
示例:
ini
const total = useMemo(() => {
return list.reduce((sum, item) => sum + item.price, 0);
}, [list]);
5. useCallback
作用:缓存函数
适合场景:
- 把函数传给子组件时避免重复创建
- 配合
React.memo做性能优化
示例:
ini
const handleClick = useCallback(() => {
console.log("点击了");
}, []);
6. useContext
作用:跨层级共享数据
适合场景:
- 主题切换
- 用户信息共享
- 全局配置共享
示例:
ini
const theme = useContext(ThemeContext);
十三、初学者最容易犯的几个错误
1. 把 useEffect 当成"任何逻辑都往里塞"
有些初学者一学会 useEffect,就恨不得什么都丢进去。
其实不是所有逻辑都要写进 useEffect。
原则是:
只有那些"渲染之后要做的事",才适合写进 useEffect。
如果只是简单计算数据,很多时候直接在组件里写就可以。
2. 在 useEffect 里漏掉依赖
比如:
scss
useEffect(() => {
console.log(count);
}, []);
如果你的副作用里用到了 count,通常就应该把它写进依赖数组里:
scss
useEffect(() => {
console.log(count);
}, [count]);
否则可能会出现数据不是最新值的问题。
3. 把 useRef 和 useState 搞混
记住一句非常关键的话:
- 需要更新页面的,用 useState
- 只是存值但不需要更新页面的,用 useRef
这个区别一定要分清。
4. 在 if 中使用 Hook
这个是典型错误。
scss
if (flag) {
useEffect(() => {}, []);
}
千万别这么写。
Hook 一定要放在组件最外层。
十四、一个完整小案例:用 Hook 写一个待办事项列表
下面我们用 Hook 写一个简单的 Todo List,帮助你把 useState 和 useEffect 串起来理解。
javascript
import React, { useEffect, useState } from "react";
function TodoApp() {
const [inputValue, setInputValue] = useState("");
const [list, setList] = useState([]);
useEffect(() => {
const localData = localStorage.getItem("todo-list");
if (localData) {
setList(JSON.parse(localData));
}
}, []);
useEffect(() => {
localStorage.setItem("todo-list", JSON.stringify(list));
}, [list]);
const handleAdd = () => {
if (!inputValue.trim()) return;
setList([...list, inputValue]);
setInputValue("");
};
const handleDelete = (index) => {
const newList = list.filter((_, i) => i !== index);
setList(newList);
};
return (
<div>
<h2>Todo List</h2>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="请输入待办事项"
/>
<button onClick={handleAdd}>添加</button>
<ul>
{list.map((item, index) => (
<li key={index}>
{item}
<button onClick={() => handleDelete(index)}>删除</button>
</li>
))}
</ul>
</div>
);
}
export default TodoApp;
这个案例里用到了什么?
useState
管理两个状态:
- 输入框内容
inputValue - 待办列表
list
第一个 useEffect
页面第一次加载时,从本地缓存读取数据。
第二个 useEffect
每次 list 变化时,把最新数据存到 localStorage。
这个例子特别适合初学者练手,因为它同时涉及:
- 表单输入
- 列表渲染
- 状态更新
- 副作用处理
- 本地存储
十五、React Hook 和类组件相比,到底哪个好?
现在大多数新项目,几乎都更倾向于:
函数组件 + Hook
原因很简单。
Hook 的优点
1. 代码更简洁
不用写很多类组件模板代码。
2. 逻辑更聚合
一个功能相关的代码可以写在一起,不容易分散。
3. 更方便复用逻辑
自定义 Hook 非常适合抽离通用能力。
4. 更符合现在 React 的主流生态
很多现代 React 项目、组件库、教程,默认都基于 Hook。
当然,这并不是说类组件完全没用了。
只是从开发趋势来看,Hook 已经成为 React 的核心写法之一。
十六、面试中怎么回答"React Hook 是什么"?
如果你面试时被问到这个问题,可以参考下面这段回答:
React Hook 是 React 16.8 引入的一套新特性,它允许我们在函数组件中使用状态、生命周期、副作用处理、引用、上下文等能力。Hook 的出现让函数组件不再只是无状态组件,同时也让逻辑复用变得更加方便,比如可以通过自定义 Hook 抽离公共逻辑。常见的 Hook 有 useState、useEffect、useRef、useMemo、useCallback、useContext 等。
如果你想回答得更通俗一点,也可以说:
Hook 就是 React 给函数组件提供的一套能力扩展机制,让函数组件也能记数据、发请求、操作 DOM、复用逻辑。
十七、总结
学 React Hook,最重要的不是一开始就把所有 Hook 全背下来,而是先把最核心的三个搞懂:
useStateuseEffectuseRef
你只要先彻底理解这三个,React Hook 的大门基本就打开了。
最后我们再用最简单的话总结一次:
useState 是什么?
让组件记住数据。
useEffect 是什么?
让组件在渲染后执行额外操作。
useRef 是什么?
让组件获取 DOM,或者保存不会触发重渲染的值。
Hook 是什么?
Hook 就是让 React 函数组件拥有状态管理、副作用处理、DOM 操作和逻辑复用能力的一套机制。
如果你之前一直觉得 Hook 很抽象,那看到这里,你至少应该已经明白:
Hook 并不是什么高深魔法,它就是 React 给函数组件配的工具箱。
十八、写在最后
对于 React 初学者来说,Hook 一开始确实会有点绕,尤其是:
- 为什么要用
useState - 为什么
useEffect有依赖数组 - 为什么 Hook 不能写在 if 里
- 为什么函数组件每次重新执行却还能记住状态
这些问题,几乎每个 React 学习者都会遇到。
但只要你多写几个小例子,比如:
- 计数器
- 输入框联动
- Todo List
- 页面请求数据
- 输入框自动聚焦
Hook 很快就会从"抽象概念"变成"顺手工具"。
建议你下一步重点练这几个方向:
useState控制表单和列表useEffect做接口请求和事件监听useRef获取 DOM 和保存临时值- 尝试自己写一个简单的自定义 Hook
当你把这些练熟以后,再去学 useMemo、useCallback、useContext,会轻松很多。