1. useState
- 状态管理
作用
在函数组件中声明和更新局部状态。
语法
const [state, setState] = useState(initialValue);
state
:当前状态值。setState
:更新状态的函数(类似this.setState
)。initialValue
:初始状态(可以是任意类型)。
使用场景
- 管理组件内部的状态(如计数器、表单输入、开关状态等)。
- 替代类组件的
this.state
和this.setState
。
示例
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
2. useEffect
- 副作用处理
作用
在组件渲染后执行副作用操作(如数据获取、订阅、手动 DOM 操作等)。
语法
useEffect(() => {
// 副作用逻辑
return () => {
// 清理逻辑(可选)
};
}, [dependencies]); // 依赖项数组
- 无依赖(
[]
) :仅在组件挂载和卸载时执行(类似componentDidMount
+componentWillUnmount
)。 - 有依赖(
[dep1, dep2]
) :依赖变化时执行。 - 无依赖数组:每次渲染后都执行(慎用)
注意:
- 挂载时执行:传入
useEffect
的函数会在组件首次渲染后执行(即componentDidMount
的时机)。 - 卸载时执行:如果该
useEffect
返回一个清理函数(cleanup),则清理函数会在组件卸载时执行(即componentWillUnmount
的时机)。
示例代码:
import React, { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
console.log('组件挂载时执行(类似 componentDidMount)');
return () => {
console.log('组件卸载时执行(类似 componentWillUnmount)');
};
}, []); // 空依赖数组表示仅在挂载和卸载时执行
return <div>My Component</div>;
}
关键点:
- 如果
useEffect
没有返回清理函数,则卸载时不会执行任何操作。 - 依赖数组
[]
确保副作用仅在挂载和卸载时运行,不会因 props 或 state 变化而重新执行。
总结:
- 挂载时:执行
useEffect
的主函数。 - 卸载时:执行
useEffect
返回的清理函数(如果有的话)。 - 如果没有清理函数,卸载时不会执行任何操作。
使用场景
- 数据获取(
fetch
、axios
)。 - 事件监听(
addEventListener
)。 - 定时器(
setInterval
/setTimeout
)。 - 手动修改 DOM。
示例
import { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data')
.then(res => res.json())
.then(data => setData(data));
}, []); // 仅在组件挂载时执行
return <div>{data ? data.name : 'Loading...'}</div>;
}
3. useContext
- 跨组件数据共享
作用
访问 React Context 的值,避免层层传递 props,避免手动逐层传递 props(即 "prop drilling")。它通常与 createContext
配合使用,适用于全局或主题、用户身份等需要多组件访问的数据。
语法
const value = useContext(MyContext);
MyContext
:由React.createContext()
创建的 Context 对象。
使用场景
- 全局主题、用户身份、语言切换等共享数据。
1. 基本使用步骤
(1) 创建 Context
import { createContext } from 'react';
// 创建一个 Context 并设置默认值(默认值在未提供 Provider 时生效)
const ThemeContext = createContext('light'); // 'light' 是默认值
(2) 用 Provider 包裹组件并传递数据
function App() {
const [theme, setTheme] = useState('dark');
return (
// 通过 Provider 的 value 属性共享数据(此处共享 theme 和 setTheme)
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
(3) 在子组件中使用 useContext
获取数据
import { useContext } from 'react';
function Button() {
// 直接获取 ThemeContext 的数据
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
style={{ background: theme === 'dark' ? '#333' : '#eee' }}
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
Toggle Theme
</button>
);
}
关键特性
特性 | 说明 |
---|---|
跨组件共享数据 | 无需通过 props 层层传递,任意层级的子组件均可直接访问 Context 数据。 |
性能优化 | 当 Context 的 value 变化时,所有用到该 Context 的组件都会重新渲染。 |
默认值 | 如果组件不在 Provider 的包裹下,useContext 会返回 createContext 的默认值。 |
适用场景
- 全局主题(如暗黑模式切换)。
- 用户身份信息(如当前登录用户)。
- 多语言国际化(如切换语言包)。
- 全局配置(如 API 端点、功能开关)。
性能注意事项
-
避免频繁更新的 Context:如果 Context 的 value 经常变化(如每秒更新的状态),可能导致大量子组件重新渲染。解决方案:
- 拆分 Context(将稳定数据和易变数据分开)。
- 使用
React.memo
优化子组件。
示例:拆分 Context
// 将 theme 和 setTheme 分开,避免不必要的渲染
const ThemeContext = createContext('light');
const ThemeUpdateContext = createContext(() => {});
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<ThemeUpdateContext.Provider value={setTheme}>
<Toolbar />
</ThemeUpdateContext.Provider>
</ThemeContext.Provider>
);
}
// 只有用到 setTheme 的组件会订阅更新
function Button() {
const theme = useContext(ThemeContext);
const setTheme = useContext(ThemeUpdateContext);
// ...
}
ThemeContext.Provider
是 React Context API 的核心部分,它负责向下传递共享的数据,所有被它包裹的子组件都可以通过 useContext(ThemeContext)
获取到这些数据。下面详细解释它的作用和工作原理:
1. ThemeContext.Provider
是什么?
- 来源:通过
createContext()
创建 Context 对象时,会自动生成一个Provider
组件(这里是ThemeContext.Provider
)。 - 作用:它是一个组件,通过
value
属性向其所有子组件(无论层级多深)提供共享数据。 - 类比:类似一个“数据管道”,包裹在它内部的组件可以直接“接上”这个管道获取数据,无需手动逐层传递 props。
-
2. 基本语法
import { createContext } from 'react';
// 1. 创建 Context
const ThemeContext = createContext('light'); // 'light' 是默认值
function App() {
const [theme, setTheme] = useState('dark');
// 2. 用 Provider 包裹需要共享数据的组件
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<ChildComponent />
</ThemeContext.Provider>
);
}
3. 核心特点
特性 | 说明 |
---|---|
数据共享 | 所有子组件(如 <ChildComponent /> )均可通过 useContext(ThemeContext) 获取 value 的数据。 |
value 是必需的 | 必须通过 value 属性传递数据,没有 value 会导致子组件获取默认值或报错。 |
动态更新 | 如果 value 变化,所有消费该 Context 的子组件会自动重新渲染。 |
4. 为什么需要 Provider?
❌ 不用 Provider 的问题
// 如果直接使用 Context,但没有 Provider 包裹:
function Button() {
const theme = useContext(ThemeContext);
// 这里获取的是 createContext 的默认值('light'),无法动态更新
}
✅ 使用 Provider 的优势
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{/* 子组件可以获取到最新的 theme 和 setTheme */}
<Button />
</ThemeContext.Provider>
);
}
5. 多层 Provider 嵌套
如果存在多个同类型 Provider,子组件会获取最近的 Provider 的值:
<ThemeContext.Provider value="dark">
<Header /> {/* 这里获取到 'dark' */}
<ThemeContext.Provider value="light">
<Button /> {/* 这里获取到 'light' */}
</ThemeContext.Provider>
</ThemeContext.Provider>
6. 性能优化建议
-
避免频繁变化的 value:如果 Provider 的
value
是一个新对象(如value={{ theme, setTheme }}
),每次渲染都会生成新对象,导致所有子组件重新渲染。解决方案:// 优化:使用 useMemo 缓存 value const value = useMemo(() => ({ theme, setTheme }), [theme]); return <ThemeContext.Provider value={value}>...</ThemeContext.Provider>;
4. useMemo
- 缓存计算结果
作用
缓存昂贵的计算,避免每次渲染都重新计算。
语法
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- 仅在
a
或b
变化时才重新计算。
使用场景
- 优化复杂计算(如排序、筛选大数据)。
- 避免子组件不必要的重渲染。
示例
import { useMemo } from 'react';
function ExpensiveComponent({ list }) {
const sortedList = useMemo(() => {
return list.sort((a, b) => a - b);
}, [list]);
return <div>{sortedList.join(', ')}</div>;
}
5. useCallback
- 缓存函数
作用
缓存函数,避免子组件因函数引用变化而重渲染。
语法
const memoizedFn = useCallback(() => {
doSomething(a, b);
}, [a, b]);
使用场景
- 优化子组件(如
React.memo
包裹的组件)。 - 防止因父组件重渲染导致子组件不必要的更新。
示例
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Clicked!', count);
}, [count]);
return <Child onClick={handleClick} />;
}
const Child = React.memo(({ onClick }) => {
return <button onClick={onClick}>Click Me</button>;
});
6. useRef
- 引用 DOM 或保存可变值
作用
- 访问 DOM 节点。
- 保存可变值(不触发重渲染)。
语法
const ref = useRef(initialValue);
ref.current; // 访问当前值
使用场景
- 获取输入框焦点。
- 存储定时器 ID、上一次渲染的值等。
示例
import { useRef } from 'react';
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
useRef
和 useState
是 React Hooks 中两个常用的 Hook,它们的主要区别在于用途、触发渲染、数据可变性等方面。
存储上次的值
import { useState, useEffect, useRef } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count; // 修改 ref 不会触发渲染
}, [count]);
return (
<div>
<p>Now: {count}, Before: {prevCountRef.current}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
特性 | useState | useRef |
---|---|---|
用途 | 管理组件状态,状态变化触发重新渲染 | 存储可变值,变化不触发重新渲染 |
返回值 | [state, setState] (状态 + 更新函数) | { current: value } (可变 ref 对象) |
是否触发渲染 | ✅ 状态更新会触发组件重新渲染 | ❌ 修改 ref.current 不会触发渲染 |
数据可变性 | 不可变(应使用 setState 更新) | 可变(直接修改 ref.current ) |
适用场景 | 需要 UI 响应的数据(如表单输入、计数器) | 存储 DOM 引用、保存上一次的值、不触发渲染的变量 |
关键区别总结
场景 | 用 useState | 用 useRef |
---|---|---|
需要 UI 更新的数据(如计数器) | ✅ 适合 | ❌ 不适合(不会触发渲染) |
存储 DOM 引用 | ❌ 不适合 | ✅ 适合 |
记录上一次的 props/state | ❌ 不适合(会触发额外渲染) | ✅ 适合 |
存储定时器、动画 ID | ❌ 不适合(可能导致渲染问题) | ✅ 适合 |
简单记忆:
- 需要触发渲染的数据 →
useState
- 需要存储可变值但不触发渲染 →
useRef
7. useReducer
- 复杂状态管理
作用
类似 Redux 的状态管理,适合复杂逻辑。
语法
const [state, dispatch] = useReducer(reducer, initialState);
reducer
:(state, action) => newState
函数。dispatch
:发送 action 触发更新。
使用场景
- 表单状态管理。
- 替代
useState
处理复杂逻辑。
示例
import { useReducer } from 'react';
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+1</button>
</div>
);
}
7.1. useReducer
的基本结构
useReducer
是 React 提供的状态管理 Hook,适合处理复杂的状态逻辑(比 useState
更结构化)。
它接收两个参数:
reducer
函数:定义如何更新状态((state, action) => newState
)。- 初始状态(这里是
{ count: 0 }
)。
返回:
- 当前状态(
state
)。 dispatch
函数:用于触发状态更新(发送action
)。
7.2. Reducer 函数(counterReducer
)
这是一个纯函数,根据 action.type
决定如何计算新状态:
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }; // 返回新状态
case 'decrement':
return { count: state.count - 1 };
default:
return state; // 未知 action 时返回原状态
}
}
- 规则:必须返回新的状态对象(不可直接修改
state
)。 - 扩展性:可以轻松添加更多操作(如
reset
、multiply
)。
7.3. 组件中使用 useReducer
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+1</button>
{/* 可以添加递减按钮: */}
<button onClick={() => dispatch({ type: 'decrement' })}>-1</button>
</div>
);
}
dispatch({ type: 'increment' })
:发送一个action
,触发reducer
计算新状态。- UI 自动更新:状态变化后,组件会重新渲染,显示最新的
state.count
。
7.4. 与 useState
的对比
场景 | useState | useReducer |
---|---|---|
适用情况 | 简单状态(如布尔值、数字) | 复杂状态逻辑或多关联状态 |
更新逻辑 | 直接通过 setState 更新 | 通过 dispatch(action) 集中管理 |
代码组织 | 分散在多个 useState | 逻辑集中在 reducer 中,更清晰 |
7.5. 何时选择 useReducer
?
- 状态逻辑较复杂(如多个子状态相互依赖)。
- 需要更可预测的状态管理(类似 Redux 的模式)。
- 需要复用或测试状态更新逻辑。
8. useLayoutEffect
- 同步副作用
作用
与 useEffect
类似,但在浏览器 绘制前 同步执行。
语法
useLayoutEffect(() => {
// DOM 测量或同步操作
}, [dependencies]);
使用场景
- 计算 DOM 尺寸或位置(如动画)。
- 避免闪烁(如强制同步渲染)。
示例
import { useLayoutEffect, useRef } from 'react';
function MeasureElement() {
const divRef = useRef(null);
useLayoutEffect(() => {
const { width } = divRef.current.getBoundingClientRect();
console.log('Width:', width);
}, []);
return <div ref={divRef}>Measure Me</div>;
}
总结
Hook | 核心用途 | 类比类组件 |
---|---|---|
useState | 管理组件内部状态 | this.state + setState |
useEffect | 处理副作用(数据获取、订阅等) | componentDidMount + componentDidUpdate + componentWillUnmount |
useContext | 跨组件共享数据 | Context.Consumer |
useMemo | 缓存计算结果 | shouldComponentUpdate |
useCallback | 缓存函数引用 | shouldComponentUpdate |
useRef | 访问 DOM 或存储可变值 | createRef() |
useReducer | 复杂状态管理 | Redux-like reducer |
useLayoutEffect | 同步 DOM 操作 | componentDidMount (同步) |