文章目录
- [一、React 进阶技巧:useState 函数式更新(Functional Updates)](#一、React 进阶技巧:useState 函数式更新(Functional Updates))
- [1. 四大核心好处](#1. 四大核心好处)
-
- [A. 确保获取"最新的"状态(解决闭包陷阱)](#A. 确保获取“最新的”状态(解决闭包陷阱))
- [B. 减少 Hook 的依赖项(性能优化)](#B. 减少 Hook 的依赖项(性能优化))
- [C. 在异步逻辑中保证安全性](#C. 在异步逻辑中保证安全性)
- [D. 逻辑解耦](#D. 逻辑解耦)
- [React 进阶笔记:useContext 全解析](#React 进阶笔记:useContext 全解析)
-
- [1. 什么是 useContext?](#1. 什么是 useContext?)
- [2. 核心使用流程(三部曲)](#2. 核心使用流程(三部曲))
-
- [第一步:创建 Context](#第一步:创建 Context)
- [第二步:提供 Provider](#第二步:提供 Provider)
- [第三步:消费 Context](#第三步:消费 Context)
- [3. seContext 的四大进阶用法](#3. seContext 的四大进阶用法)
-
- [① 动态更新 Context](#① 动态更新 Context)
- [② 封装自定义 Provider(工程化实践)](#② 封装自定义 Provider(工程化实践))
- [③ 自定义 Hook 简化调用](#③ 自定义 Hook 简化调用)
- [4. 性能陷阱与优化方案](#4. 性能陷阱与优化方案)
- [5. useContext vs. Redux:怎么选?](#5. useContext vs. Redux:怎么选?)
- [6. 核心概念总结](#6. 核心概念总结)
- [三、 useReducer 深度解析](#三、 useReducer 深度解析)
-
- [1. 核心概念:三个核心角色](#1. 核心概念:三个核心角色)
- [2. 基本语法](#2. 基本语法)
- [3. 实战演练:计数器例子](#3. 实战演练:计数器例子)
-
- [1. 定义 Reducer 函数](#1. 定义 Reducer 函数)
- [2. 在组件中使用](#2. 在组件中使用)
- [React 状态管理对比:useState vs useReducer](#React 状态管理对比:useState vs useReducer)
-
- [💡 选型建议:](#💡 选型建议:)
- [4. 进阶技巧](#4. 进阶技巧)
-
- [1. 传递 Payload(载荷)](#1. 传递 Payload(载荷))
- [2. 惰性初始化](#2. 惰性初始化)
- [四、React 状态管理指南:useState vs useReducer](#四、React 状态管理指南:useState vs useReducer)
-
- [一、 深度对比:useReducer 的核心优势](#一、 深度对比:useReducer 的核心优势)
-
- [1. 逻辑解耦:将"做什么"与"怎么做"分开](#1. 逻辑解耦:将“做什么”与“怎么做”分开)
- [2. 状态依赖关系的完美处理](#2. 状态依赖关系的完美处理)
- [3. 性能优化(稳定的引用)](#3. 性能优化(稳定的引用))
- [二、 场景模拟:你应该如何选择?](#二、 场景模拟:你应该如何选择?)
- [三、 决策树:三步定乾坤](#三、 决策树:三步定乾坤)
- [四、 进阶建议](#四、 进阶建议)
-
- [什么时候不该用 useReducer?](#什么时候不该用 useReducer?)
- [五、 一句话总结](#五、 一句话总结)
一、React 进阶技巧:useState 函数式更新(Functional Updates)
在 React 开发中,函数式更新 是指向 setState 传递一个回调函数,而不是直接传递一个值。这是处理复杂状态逻辑和性能优化的核心手段。
javascript
// 普通更新
setCount(count + 1);
// 函数式更新
setCount(prevCount => prevCount + 1);
1. 四大核心好处
A. 确保获取"最新的"状态(解决闭包陷阱)
React 的 setState 是异步执行的(在同一个事件循环中会进行批处理)。如果你在短时间内连续调用多次普通更新,可能会因为闭包导致拿到的状态是"旧的"。
错误场景(普通更新):
javascript
const handleAdd = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
// 结果:count 只增加了 1。因为三次调用时,count 的值都是同一个快照。
正确场景(函数式更新):
javascript
const handleAdd = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
// 结果:count 增加了 3。React 会保证 prev 总是上一次更新操作后的最新状态。
B. 减少 Hook 的依赖项(性能优化)
这是配合 useEffect、useCallback 或 useMemo 时最强大的好处。使用函数式更新可以移除依赖项,防止 Hook 频繁触发或导致子组件重绘。
javascript
// ❌ 必须依赖 count,导致 handleClick 每次 count 变动都重新生成
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
// ✅ 不需要依赖 count,handleClick 引用保持稳定
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
注:这对于配合 React.memo 优化子组件性能极其重要。
C. 在异步逻辑中保证安全性
在 setTimeout、setInterval 或网络请求回调中,由于闭包效应,直接访问的状态变量往往是函数创建时的旧值。
javascript
useEffect(() => {
const timer = setInterval(() => {
// 如果这里写 setCount(count + 1),count 将由于闭包永远锁定在初始值
setCount(prev => prev + 1);
}, 1000);
return () => clearInterval(timer);
}, []); // 依赖为空,timer 仅在挂载时创建一次
D. 逻辑解耦
函数式更新允许你将"如何更新状态"的逻辑提取到组件外部。因为更新函数不再依赖组件内部的具体变量,它变成了一个纯函数。
javascript
// 逻辑可以定义在组件外部,甚至跨文件复用
const increment = (prev) => prev + 1;
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(increment)}>+</button>;
}
React 进阶笔记:useContext 全解析
这是一个关于 useContext 的全方位教程笔记,旨在帮助你彻底理清 Context API 的使用逻辑、应用场景以及性能优化方案。
1. 什么是 useContext?
在 React 中,数据通常通过 props 自上而下传递。但当组件嵌套层级过深时(例如:根组件 -> 导航栏 -> 用户面板 -> 用户头像),这种逐级传递(Prop Drilling)会变得极其痛苦。
useContext 允许我们创建一个"全局广播系统",让任何层级的子组件都能直接获取到顶层定义的数据,而无需通过 props 中转。
2. 核心使用流程(三部曲)
第一步:创建 Context
使用 createContext 创建一个上下文对象。通常单独放在一个文件中。
javascript
// ThemeContext.js
import { createContext } from 'react';
// 'light' 是默认值,仅在组件未被 Provider 包裹时生效
export const ThemeContext = createContext('light');
第二步:提供 Provider
在父组件中使用 ThemeContext.Provider 包裹子组件,并通过 value 属性下发数据。
javascript
import { useState } from 'react';
import { ThemeContext } from './ThemeContext';
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value="{theme}">
<MainPage/>
</ThemeContext.Provider>
);
}
第三步:消费 Context
在任何子组件中调用 useContext Hook 即可获取数据。
javascript
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
function UserPanel() {
const theme = useContext(ThemeContext); // 直接拿到 'dark'
return <div className={theme}>当前主题:{theme}</div>;
}
3. seContext 的四大进阶用法
① 动态更新 Context
如果你需要子组件修改 Context,可以将 state 和 setState 一起放入 value 中。
javascript
// Provider 传递对象
<ThemeContext.Provider setTheme theme, value={{ }}>
{children}
</ThemeContext.Provider>
// 子组件中调用
const { theme, setTheme } = useContext(ThemeContext);
<button onClick={() => setTheme('light')}>切换主题</button>
② 封装自定义 Provider(工程化实践)
为了保持组件整洁,通常会将逻辑封装在独立组件中。
javascript
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => setTheme(t => t === 'light' ? 'dark' : 'light');
return (
<ThemeContext.Provider theme, toggleTheme value="{{" }}>
{children}
</ThemeContext.Provider>
);
}
③ 自定义 Hook 简化调用
为了避免每次都要重复导入 ThemeContext 和 useContext,建议封装一个自定义 Hook。
javascript
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme 必须在 ThemeProvider 内部使用');
}
return context;
};
4. 性能陷阱与优化方案
🚨性能痛点:无效重渲染
当 Context 的 value 发生变化时,所有使用了 useContext(MyContext) 的子组件都会强制重新渲染,即使它们只使用了对象中未改变的部分。
优化手段:
-
拆分 Context:不要把所有全局状态(用户信息、主题、国际化)塞进一个 Context。按功能拆分,减少不必要的更新范围。
-
Memo 保护:配合 React.memo 使用,确保非相关组件不触发计算。
-
useMemo 缓存 Value:
javascript
const value = useMemo(() => ({ theme, toggleTheme }), [theme]);
return (
<ThemeContext.Provider value="{value}">
{children}
</ThemeContext.Provider>
);
5. useContext vs. Redux:怎么选?
| 维度 | useContext + useState | Redux / Zustand |
|---|---|---|
| 上手难度 | 极低(React 原生支持) | 较高(需学习新概念) |
| 性能 | 中等(复杂场景易导致全量渲染) | 优秀(有精准的订阅机制) |
| 调试工具 | React DevTools | Redux DevTools (强大) |
| 适用场景 | 低频更新:主题、语言、用户信息。 | 高频更新/大型应用:电商购物车、协作工具。 |
6. 核心概念总结
| 关键词 | 解释 |
|---|---|
| createContext | 创建上下文,定义默认值。 |
| Provider | 生产者,通过 value 属性广播数据。 |
| useContext | 消费者,在子组件中提取数据。 |
| Prop Drilling | 痛点,指属性像钻头一样层层穿透中间组件。 |
💡 金句总结:
"Context 不是为了取代 Props,而是为了终结那些为了传值而传值的中间层组件。它是 React 的全局任意门,但门开多了,性能也会迷路。"
三、 useReducer 深度解析
useReducer 是 React 提供的一个用于状态管理的 Hook,它是 useState 的替代方案。当你发现组件的状态逻辑变得复杂(例如:一个状态依赖于另一个状态,或者有多个子状态需要同步更新)时,useReducer 就是你的最佳拍档。
1. 核心概念:三个核心角色
理解 useReducer 的关键在于弄清楚这三个"演员"的关系:
- State (状态) :当前的数据源(比如:
count: 0)。 - Action (动作) :描述发生了什么的对象(比如:
{ type: 'increment' })。 - Reducer (处理函数):一个纯函数,接收当前的 State 和 Action,并返回新的 State。
2. 基本语法
javascript
const [state, dispatch] = useReducer(reducer, initialState);
-
state:当前的状态值。
-
dispatch:一个分发函数,用来发送 action。调用它会触发 reducer 执行。
-
reducer:处理逻辑的函数。
-
initialState:初始状态。
3. 实战演练:计数器例子
1. 定义 Reducer 函数
Reducer 必须是一个纯函数。它不应该有副作用(如 API 请求),只负责计算新状态。
javascript
function reducer(state, action) {
// 根据 action 的类型来决定如何更新状态
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
default:
throw new Error('未知操作');
}
}
2. 在组件中使用
javascript
import React, { useReducer } from 'react';
function Counter() {
const initialState = { count: 0 };
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<h1>当前计数:{state.count}</h1>
{/* 通过 dispatch 发送 action */}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
</>
);
}
React 状态管理对比:useState vs useReducer
| 特性 | useState | useReducer |
|---|---|---|
| 数据类型 | JS 基础类型(Number, String)或简单对象 | 复杂的 Object、Array,多个互相关联的状态 |
| 逻辑位置 | 散落在组件内部的各个函数中 | 集中在 reducer 函数中,逻辑清晰 |
| 可维护性 | 状态多了以后,代码会变得凌乱 | 非常适合大型组件,方便调试和测试 |
| 性能优化 | 每次更新都可能创建新函数 | 可以通过 dispatch 传递给子组件,配合 memo 减少重绘 |
💡 选型建议:
- 优先使用
useState:当状态是独立的(如isOpen,inputValue)或者逻辑比较简单时。 - 考虑使用
useReducer:当一个动作需要同时更新多个状态,或者下一个状态依赖于前一个状态的复杂计算时。
4. 进阶技巧
1. 传递 Payload(载荷)
有时候我们不仅要告诉 Reducer "做什么",还要传递具体的数据。
javascript
// 1. dispatch 时携带数据
dispatch({ type: 'set_count', payload: 10 });
// 2. reducer 中接收数据
case 'set_count':
return { count: action.payload };
2. 惰性初始化
如果初始状态需要通过复杂计算获得,可以传入第三个参数 init 函数。这样初始状态只会计算一次,避免重复计算损耗性能。
javascript
const [state, dispatch] = useReducer(reducer, initialArg, init);
四、React 状态管理指南:useState vs useReducer
在 React 开发中,useState 和 useReducer 就像是工具箱里的"螺丝刀"与"电钻":一个轻巧便捷,一个马力十足。虽然它们都能管理状态,但底层的思维模型完全不同。
一、 深度对比:useReducer 的核心优势
1. 逻辑解耦:将"做什么"与"怎么做"分开
- useState:状态更新逻辑通常写在事件处理函数中。当逻辑复杂时,你的组件会充斥着大量的更新代码。
- useReducer :组件只负责发送指令(Dispatch Action),而复杂的逻辑被集中封装在
reducer纯函数中。这让组件变得非常干净,只关注 UI 展示。
2. 状态依赖关系的完美处理
当你的下一个状态依赖于上一个状态,或者多个状态需要同时更新时,useState 容易出现闭包陷阱或逻辑散乱。
场景例子 :在一个表单中,点击"重置"需要同时清空 5 个输入框、重置校验状态并关闭加载动画。
useReducer只需要一行dispatch({ type: 'RESET' })就能在 reducer 里统一完成,保证了原子性。
3. 性能优化(稳定的引用)
dispatch 函数在组件的整个生命周期中是地址不变 的。
这意味着你可以放心地将 dispatch 传递给子组件,而不需要担心触发子组件的不必要重绘(配合 React.memo)。相比之下,useState 的更新函数虽然也稳定,但当逻辑需要包裹多层 useCallback 时,代码会变得极其臃肿。
二、 场景模拟:你应该如何选择?
为了直观决定,可以参考下表:
| 维度 | 推荐使用 useState |
推荐使用 useReducer |
|---|---|---|
| 状态复杂度 | 基础类型(数字、布尔)或简单对象 | 嵌套对象、数组,或多个互相影响的状态 |
| 逻辑复杂度 | 简单的更新(如 setCount(c + 1)) |
复杂的业务逻辑(需要 switch/case 区分) |
| 关联性 | 各个状态独立(如 name 和 age) |
状态间有关联(如 loading 随 data 改变) |
| 组件规模 | 中小型组件,逻辑不跨组件 | 大型组件,或需要深度传递状态的场景 |
| 测试需求 | 较难独立测试 UI 中的逻辑 | 极佳。reducer 是纯函数,可脱离 UI 测试 |
三、 决策树:三步定乾坤
当你犹豫不决时,问自己三个问题:
- "我的状态变量是否超过 3-5 个且互有关联?"
- 如果是(例如:处理一个复杂的注册表单),选 useReducer。
- "我是否需要把状态更新逻辑传给深层的子组件?"
- 如果是,选 useReducer(配合 Context API 效果更佳,能避免 Prop Drilling)。
- "我是不是在写类似
setA(a + 1); setB(!b); setC(data);这种连续调用?"- 如果是,说明这些状态属于同一个"事务",选 useReducer。
四、 进阶建议
什么时候不该用 useReducer?
- 过度设计 :如果只是控制一个
isOpen的开关,非要写 reducer、action 和 dispatch,那就是在浪费生命,增加代码阅读负担。 - 异步逻辑 :记住 reducer 必须是纯函数 。如果你有大量的
fetch请求,逻辑应该写在useEffect或自定义 Hook 中,最后只把结果 dispatch 给 reducer。
五、 一句话总结
- 用
useState处理局部、简单、独立的小状态; - 用
useReducer处理全局、复杂、关联的业务逻辑。