目录
- useReducer
-
- [useReducer和 useState](#useReducer和 useState)
- useContext
- useMemo
- React.memo
- useCallback
useReducer
useReducer 是一个用于管理复杂状态逻辑的 Hook,可以看作是 useState 的进阶版。当你的状态更新逻辑涉及多个子值,或者下一次状态依赖于上一次状态时,它能让代码更加清晰、可预测。
工作机制:
它的核心思想是将状态 和如何更新状态的逻辑分离开:
- 状态 (state):当前的数据。
- 动作 (action):一个描述"发生了什么"的普通对象,通常有一个 type 字段。
- 分发函数 (dispatch):触发状态更新的方法,你把 action 传给它。
- 归约函数 (reducer):一个纯函数,它接收当前的 state 和 action,然后计算并返回一个全新的 state。
工作流程:

基本使用:
- 导入 useReducer。
- 定义 reducer 函数:使用 switch 语句根据 action.type 来返回新的状态。
- 定义 initialState:状态的初始值。
- 在组件中调用 useReducer:它返回一个数组,包含当前状态(state)和一个分发函数(dispatch)。
代码:计数器功能
js
import { useReducer } from 'react';
type State = {
count: number;
}
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'reset' }
// 1. 定义初始状态
const initialState: State = { count: 0 }
// 2. 定义 reducer 函数,根据 action 的类型更新状态
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
default:
return state;
}
}
export const UseReducer = () => {
// 3. 使用 useReducer,传入 reducer 和初始状态,得到当前状态和 dispatch 函数
const [state, dispatch] = useReducer(reducer, initialState)
return (
<div>
<h2>useReducer 示例</h2>
<p>计数: {state.count}</p>
{/* 通过调用 dispatch 并传入 action 对象来触发状态更新 */}
<button onClick={ () => dispatch({type: 'increment'})}>+</button>
<button onClick={ () => dispatch({type: 'decrement'})}>-</button>
<button onClick={ () => dispatch({type: 'reset'})}>重置</button>
</div>
)
}
useReducer和 useState
useReducer和 useState都是 React 中用于管理组件状态的 Hook,但它们有不同的适用场景和使用方式。
useState
- 简单、独立的状态管理
- 基于函数更新:setState(newValue)或 setState(prev => newValue)
- 适合的场景:
- 布尔值开关(如 loading、modal 显示)
- 简单的计数器
- 表单输入值
- 单个数据项
useReducer
- 复杂状态逻辑管理
- 基于动作(action):通过 dispatch(action)触发状态更新
- 适合的场景:
- 状态之间有复杂依赖关系
- 状态逻辑复杂,需要提取复用
- 状态结构复杂(如嵌套对象、数组)
- 需要追溯状态变化历史
如何选择?
使用 useState 的情况:
- 状态是独立的基本类型
- 状态逻辑简单
- 不需要复用状态逻辑
- 组件内部状态,不涉及全局共享
使用 useReducer 的情况:
- 状态之间有复杂依赖
- 状态结构复杂(多个子值)
- 需要可预测的状态变化
- 状态逻辑需要复用或测试
- 下一个状态依赖前一个状态
- 需要实现撤销/重做功能
useContext
useContext是 React 提供的一个 Hook,用于在函数组件中轻松消费 React 上下文(Context),解决组件间数据传递的问题,避免了层层传递 props 的繁琐(称为 "prop drilling")。
什么是prop drilling
当父组件要传递数据给重孙组件时,要层层传递:父组件 -> 子组件 -> 孙组件 -> 重孙组件
useContext语法
- 创建:创建一个盒子(存放数据的地方)
js
const MyData = createContext('默认值');
- 包裹:把数据放到盒子里
js
// 在父组件使用 Provider 提供数据
function father() {
const someData = "要传递的数据";
return (
{/* 用 MyData.Provider 包裹,把要传递的数据放到 value属性 */}
<MyData.Provider value={someData}>
<child />
</MyData.Provider>
);
}
- 使用:在用到数据的地方拿出来使用
js
// 在任何子组件中获取
function grandson() {
const receivedData = useContext(MyData);
return <div>{receivedData }</div>;
}
使用
ThemeContext.ts
js
// 引入createContext
import { createContext } from "react";
// 创建一个主题上下文,包含当前主题和切换主题的函数。提供默认值
export const ThemeContext = createContext({
theme: 'light',
toggleTheme: () => {},
});
Parent.ts
js
import { Child } from "./Child";
import { useState } from "react";
// 引入主题上下文
import { ThemeContext } from "./ThemeContext";
export const Parent = () => {
const [theme, setTheme] = useState('light');
// 切换主题的函数
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
}
return (
// 使用 ThemeContext.Provider 包裹子组件,并传入当前主题和切换主题的函数
<ThemeContext.Provider value={{ theme, toggleTheme}}>
<Child />
{/* 在父组件中添加一个按钮,点击后切换主题 */}
<button onClick={toggleTheme}>
切换主题
</button>
</ThemeContext.Provider>
);
}
Child.ts
跟useContext操作没啥关系,起一个承上启下的引用作用,只是为了内容完整展示出来,基本可以无视。
js
import {GrandChild} from "./GrandChild";
export const Child = () => {
return (
<div>
<GrandChild />
</div>
);
}
GrandChild.ts
js
import { useContext } from "react";
// 引入主题上下文
import { ThemeContext } from "./ThemeContext";
export const GrandChild = () => {
// 使用 useContext 获取当前主题
const { theme } = useContext(ThemeContext);
return (
<div>
<h3>孙组件 ------ {theme}</h3>
</div>
);
}
此时,父组件的数据可以不用经过层层传递才来到祖孙组件。
// useContext 就像:
1. 我有个快递柜(createContext)
2. 我把包裹放进去(Provider value={包裹})
3. 你在任何地方都能取出来用(useContext)
// 以前传递数据(麻烦):
爸爸 → 儿子 → 孙子 → 重孙
// 现在用 useContext(简单):
爸爸 → 快递柜 → 重孙(直接取)
在实际项目中我们通常把 useContext 封装起来。
useTheme.ts
js
import { useContext } from "react";
import { ThemeContext } from "./ThemeContext";
export const useTheme = () => {
// 使用 useContext 获取当前主题
const { theme } = useContext(ThemeContext);
return theme;
}
GrandChild.ts 改为
js
import { useTheme } from "./useTheme";
export const GrandChild = () => {
// 使用自定义的 useTheme Hook 获取当前主题
const theme = useTheme();
return (
<div>
<h3>孙组件 ------ {theme}</h3>
</div>
);
}
注:
- 必须在 Provider 内部才能拿到值,否则拿默认值
- Provider 的 value 变化,所有用 useContext 的组件都更新
- 可以创建多个 Context,分别放不同数据。按功能拆分,不要把所有状态放在一个 Context
什么时候应该使用 useContext?
- 当需要跨多层级组件共享状态时
- 全局状态如:用户信息、主题、语言、通知等
- 避免 props drilling 时
useContext 和 Redux 的区别?
- useContext + useReducer:适合中小型应用,内置于 React
- Redux:适合大型复杂应用,有强大的中间件和开发者工具
Context 更新会导致所有子组件重新渲染吗?
- 是的,但可以通过以下方式优化:
- 拆分 Context
- 使用 React.memo
- 使用 useMemo记忆 context 值
useMemo
useMemo是 React 中的一个 Hook,用于缓存计算结果,避免在每次渲染时都进行昂贵的计算。
基本语法: const result = useMemo(() => compute(), deps)
js
const result = useMemo(
// 第一个参数:计算函数
() => {
// 1. 这里可以包含任意逻辑
// 2. 必须是同步函数
// 3. 应该是纯函数(无副作用)
// 4. 返回任意类型的值
return someValue;
},
// 第二个参数:依赖数组
[]
);
返回值: result:接收 计算函数 的返回值,缓存计算的结果。
参数:
- 第一个参数:() => compute(),计算函数 (factory function),返回任意类型的值。记得 return
- 第二个参数:依赖数组,决定何时重新计算
依赖数组的三种状态:
- 空数组 []- 只计算一次
js
const stableValue = useMemo(() => {
console.log('这个只会在首次渲染时计算一次');
return expensiveInitialization();
}, []); // 空数组
- 有依赖项 - 依赖变化时重新计算
js
const dynamicValue = useMemo(() => {
console.log('依赖变化时重新计算');
return calculate(a, b, c);
}, [a, b, c]); // 指定依赖
- 不传依赖 - 每次渲染都重新计算(不推荐)
js
const alwaysRecalc = useMemo(() => {
console.log('每次渲染都计算,相当于没用 useMemo!');
return calculate();
}); // 不传依赖数组
依赖比较的详细规则:浅比较
- 基本类型的比较:比较值是否相等
js
function BasicTypes({ count, name }) {
const result = useMemo(() => {
return `Count: ${count}, Name: ${name}`;
}, [count, name]); // 基本类型:比较值是否相等
return <div>{result}</div>;
}
- 引用类型的比较:比较引用地址
js
function ReferenceTypes({ config }) {
const result = useMemo(() => {
return process(config);
}, [config]); // 对象:比较引用地址
// 问题:即使 config 内容相同,引用不同也会重新计算
return <div>{result}</div>;
}
一般对象依赖我们都是用对象属性,而不是整个对象,避免引起不要的渲染
js
function ObjectDeps({ config }) {
const result = useMemo(() => {
return config.value1 + config.value2;
}, [config.value1, config.value2]); // 使用具体属性,而不是整个对象
return <div>{result}</div>;
}
useMemo缓存的是计算的结果,而不是计算的过程。它通过比较依赖数组来决定是否需要重新计算,并返回稳定引用的值。
React.memo
当父组件重新渲染时,想要防止不必要的子组件重新渲染。我们可以用 React.memo 来优化性能,特别是对于渲染成本较高的组件。
基本语法
js
const MyComponent= (props) => <div>{props.text}</div>;
const MemoizedComponent = React.memo(MyComponent, areEqual?);
返回值 :返回一个全新的组件,这个新组件包装了原始组件,并添加了记忆(缓存)功能。不是修改原始组件
参数:
- 第一个参数:要包装的组件,必须是函数组件(包括箭头函数组件)。
- 第二个参数:可选的比较函数
js
type CompareFunction = (
prevProps: Readonly<P>, // 上一次的 props
nextProps: Readonly<P> // 下一次的 props
) => boolean; // true: 跳过渲染,false: 重新渲染
js
const ActionButton = React.memo(
({ label, onClick, icon }) => {
return (
<button onClick={onClick}>
{icon && <span>{icon}</span>}
{label}
</button>
);
},
(prevProps, nextProps) => {
// 比较 label 和 icon
return (
prevProps.label === nextProps.label &&
prevProps.icon === nextProps.icon
);
}
);
如果不提供第二个参数,React 使用默认的浅比较
js
import { memo, useState } from "react";
const MomoSon = memo(() => {
console.log('子组件渲染了');
return (
<div>
<h3>子组件</h3>
</div>
);
})
export const Parent = () => {
const [count, setCount] = useState(0);
return (
<div>
<h2>父组件</h2>
<button onClick={() => setCount(count + 1)}>
{count}
</button>
{/* 子组件不会打印console */}
<MomoSon />
</div>
);
}
useCallback
useCallback是 React 中的一个 Hook,它的作用是缓存函数引用,避免不必要的重新创建函数,从而优化性能。
主要作用
- 避免子组件不必要的重新渲染 - 当父组件重新渲染时,传递给子组件的函数引用会改变,导致子组件即使使用了 React.memo也会重新渲染
- 依赖数组优化 - 只有在依赖项发生变化时,才会重新创建函数
- 配合其他 Hook 使用 - 常与 useMemo、memo配合使用
基本语法
js
import { useCallback } from 'react';
const memoizedCallback = useCallback(
() => {
// 函数逻辑
doSomething(a, b);
},
[a, b], // 依赖数组
);
依赖数组的两种状态:
- 空数组,只在组件挂载时创建一次
js
import React, { useState, useCallback, memo } from 'react';
// 子组件
const Child = memo(({ onClick }) => {
console.log('Child 重新渲染了');
return <button onClick={onClick}>点击</button>;
});
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
// 不使用 useCallback - 每次父组件渲染都会创建新函数
// const handleClick = () => console.log('clicked');
// 使用 useCallback - 函数引用保持不变
const handleClick = useCallback(() => {
console.log('clicked');
}, []); // 空依赖数组,只在组件挂载时创建一次
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<Child onClick={handleClick} />
</div>
);
};
- 数组有值,当值发生改变时,重新创建函数
js
const MyComponent = ({ userId, data }) => {
const [count, setCount] = useState(0);
// 当 userId 或 data 变化时才重新创建函数
const fetchData = useCallback(async () => {
const result = await fetch(`/api/user/${userId}`, {
body: JSON.stringify(data)
});
return result;
}, [userId, data]); // 依赖项
useEffect(() => {
fetchData();
}, [fetchData]); // fetchData 引用稳定
return <div>Count: {count}</div>;
};
与 useMemo 的区别
- useCallback 缓存函数
- useMemo 缓存值
结合 React.memo 使用
js
const ExpensiveComponent = memo(({ onClick, data }) => {
// 昂贵计算
return <div onClick={onClick}>{data}</div>;
});
const Parent = () => {
const [state, setState] = useState();
const handleClick = useCallback(() => {
// 处理点击
}, []);
return <ExpensiveComponent onClick={handleClick} data={state} />;
};
记住:**不要过早优化。**大多数情况下,函数创建的开销很小,只有在性能确实成为问题时再考虑使用 useCallback。