React Hooks
1. useState
用于在函数组件中添加状态管理功能。它让函数组件能够拥有自己的内部状态。
(1)基本语法
const [state, setState] = useState(initialValue);
state: 当前状态值setState: 更新状态的函数initialValue: 状态的初始值
(2)重要特性
- 状态更新是异步的
function AsyncExample() {
const [count, setCount] = useState(0);
const handleClick = () => {
console.log('点击前:', count); // 0
setCount(count + 1);
console.log('点击后:', count); // 仍然是 0,因为状态更新是异步的
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>增加</button>
</div>
);
}
- 函数式更新:当新状态依赖于前一个状态时,需使用函数式更新:
function Counter() {
const [count, setCount] = useState(0);
const incrementTwice = () => {
// 错误方式:可能不会按预期工作
// setCount(count + 1);
// setCount(count + 1);
// 正确方式:使用函数式更新
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={incrementTwice}>增加2</button>
</div>
);
}
- React 会对多个状态更新进行批处理:
function BatchingExample() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
const handleClick = () => {
// 这些更新会被批处理,只触发一次重新渲染
setCount(c => c + 1);
setFlag(f => !f);
};
console.log('渲染'); // 只会打印一次
return (
<div>
<p>Count: {count}</p>
<p>Flag: {flag.toString()}</p>
<button onClick={handleClick}>更新</button>
</div>
);
}
2. useEffect
用于处理副作用,包括:请求数据、设置订阅、操作 DOM、定时器、清理资源。
(1)基本语法
useEffect(() => {
console.log('组件挂载或更新');
return () => {
console.log('组件卸载或清理');
};
}, [count]); // 依赖项改变时才执行
第二个参数是依赖数组: 空数组 []时,只在挂载时执行一次;有依赖项时,依赖项变化时执行;无依赖数组时,每次渲染都执行。
(2)在return 中,可以取消请求、清理定时器、移除事件监听器
// 取消请求
function SearchResults({ query }) {
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!query) {
setResults([]);
return;
}
const abortController = new AbortController();
const searchData = async () => {
try {
setLoading(true);
const response = await fetch(`/api/search?q=${query}`, {
signal: abortController.signal
});
const data = await response.json();
setResults(data);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('搜索失败:', error);
}
} finally {
setLoading(false);
}
};
searchData();
// 清理函数:取消请求
return () => {
abortController.abort();
};
}, [query]);
return (
<div>
{loading && <div>搜索中...</div>}
<ul>
{results.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
);
}
3. useContext
用于在组件树中跨层级传递数据,避免了逐层传递 props的问题。能够在不通过 props 的情况下,将数据传递给深层嵌套的组件。
- Context: 上下文对象,用于存储共享数据
- Provider: 提供者组件,用于提供数据
- Consumer: 消费者组件,用于使用数据
import React, { createContext, useContext, useState } from 'react';
// 1. 创建 Context
const ThemeContext = createContext();
// 2. 创建 Provider 组件
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. 在子组件中使用 Context
function Header() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<header style={{
backgroundColor: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff'
}}>
<h1>我的应用</h1>
<button onClick={toggleTheme}>
切换到 {theme === 'light' ? '深色' : '浅色'} 模式
</button>
</header>
);
}
function Content() {
const { theme } = useContext(ThemeContext);
return (
<main style={{
backgroundColor: theme === 'light' ? '#f5f5f5' : '#222',
color: theme === 'light' ? '#333' : '#fff',
padding: '20px'
}}>
<p>当前主题: {theme}</p>
</main>
);
}
// 4. 在应用中使用
function App() {
return (
<ThemeProvider>
<Header />
<Content />
</ThemeProvider>
);
}
4. useRef
用于获取 DOM 节点或保存可变值,useRef 返回一个对象,该对象有一个 current 属性,可以通过修改 current 来存储任何值。与 useState 不同的是,修改 useRef 的值不会触发组件重新渲染。
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>聚焦输入框</button>
</div>
);
}
5. useMemo
可以缓存计算结果,只有在依赖项发生变化时才会重新计算。
(1)基本语法
import { useMemo } from 'react';
const memoizedValue = useMemo(() => {
return expensiveCalculation(a, b);
}, [a, b]);
useMemo 接收两个参数:
- 计算函数:返回需要缓存的值
- 依赖数组:当数组中的值发生变化时,才会重新执行计算函数
6. useCallback
用于缓存函数,返回一个记忆化的回调函数,只有在依赖项发生变化时才会重新创建函数。
import { useCallback } from 'react';
const memoizedCallback = useCallback(() => {
// 函数逻辑
}, [dependency1, dependency2]);
useCallback 接收两个参数:
- 回调函数:需要缓存的函数
- 依赖数组:当数组中的值发生变化时,才会重新创建函数
7. useReducer
useState 的替代方案,特别适用于复杂的状态逻辑管理。
import { useReducer } from 'react';
const [state, dispatch] = useReducer(reducer, initialState);
function Counter() {
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
default:
throw new Error();
}
}
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>计数: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
</div>
);
}
Redux 与 React 使用指南
Redux 简介
Redux 是一个可预测的状态管理库,专为 JavaScript 应用程序设计。它帮助您管理应用程序的状态,使状态变化可预测、可调试。
为什么需要 Redux?
在大型 React 应用中,组件间的数据传递变得复杂:
- 多层组件需要共享状态
- 状态提升导致 props 层层传递
- 组件状态管理混乱
- 调试困难
Redux 通过集中式状态管理解决这些问题。
Redux 核心概念
三大原则
- 单一数据源:整个应用的状态存储在一个 store 的对象树中
- 状态只读:改变状态的唯一方法是触发 action
- 纯函数执行修改:使用纯函数 reducer 来执行状态修改
核心组件
Store:保存应用状态的容器
getState():获取当前状态dispatch(action):分发 actionsubscribe(listener):注册状态变化监听器
Action:描述发生什么的普通对象
{
type: 'ADD_TODO',
payload: { id: 1, text: '学习 Redux' }
}
Reducer:纯函数,描述 action 如何改变状态
(state, action) => newState
在 React 中使用 Redux
安装依赖
# 传统 Redux
npm install redux react-redux
# 推荐:Redux Toolkit
npm install @reduxjs/toolkit react-redux
基础示例:计数器应用
1. 创建 Store 和 Reducer
// store/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer
}
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
2. 在应用中提供 Store
// App.jsx
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './store';
import Counter from './components/Counter';
function App() {
return (
<Provider store={store}>
<div className="App">
<h1>Redux Counter App</h1>
<Counter />
</div>
</Provider>
);
}
export default App;
3. 在组件中使用 Redux
// components/Counter.jsx
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from '../store/counterSlice';
function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
const [incrementAmount, setIncrementAmount] = useState('2');
return (
<div>
<div>
<button onClick={() => dispatch(increment())}>+</button>
<span>{count}</span>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
<div>
<input
value={incrementAmount}
onChange={(e) => setIncrementAmount(e.target.value)}
/>
<button
onClick={() => dispatch(incrementByAmount(Number(incrementAmount) || 0))}
>
Add Amount
</button>
</div>
</div>
);
}
export default Counter;
完整示例:Todo 应用
1. 创建 Todo Slice
// store/todoSlice.js
import { createSlice } from '@reduxjs/toolkit';
const todoSlice = createSlice({
name: 'todos',
initialState: {
items: [],
filter: 'all' // all, active, completed
},
reducers: {
addTodo: (state, action) => {
state.items.push({
id: Date.now(),
text: action.payload,
completed: false
});
},
toggleTodo: (state, action) => {
const todo = state.items.find(item => item.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
deleteTodo: (state, action) => {
state.items = state.items.filter(item => item.id !== action.payload);
},
setFilter: (state, action) => {
state.filter = action.payload;
},
clearCompleted: (state) => {
state.items = state.items.filter(item => !item.completed);
}
}
});
export const { addTodo, toggleTodo, deleteTodo, setFilter, clearCompleted } = todoSlice.actions;
export default todoSlice.reducer;
// Selectors
export const selectTodos = (state) => state.todos.items;
export const selectFilter = (state) => state.todos.filter;
export const selectFilteredTodos = (state) => {
const todos = selectTodos(state);
const filter = selectFilter(state);
switch (filter) {
case 'active':
return todos.filter(todo => !todo.completed);
case 'completed':
return todos.filter(todo => todo.completed);
default:
return todos;
}
};
2. Todo 组件
// components/TodoApp.jsx
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
addTodo,
toggleTodo,
deleteTodo,
setFilter,
clearCompleted,
selectFilteredTodos,
selectFilter
} from '../store/todoSlice';
function TodoApp() {
const [inputValue, setInputValue] = useState('');
const todos = useSelector(selectFilteredTodos);
const filter = useSelector(selectFilter);
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
if (inputValue.trim()) {
dispatch(addTodo(inputValue.trim()));
setInputValue('');
}
};
return (
<div>
<h2>Todo List</h2>
{/* 添加 Todo */}
<form onSubmit={handleSubmit}>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="添加新任务..."
/>
<button type="submit">添加</button>
</form>
{/* 过滤器 */}
<div>
<button
onClick={() => dispatch(setFilter('all'))}
className={filter === 'all' ? 'active' : ''}
>
全部
</button>
<button
onClick={() => dispatch(setFilter('active'))}
className={filter === 'active' ? 'active' : ''}
>
未完成
</button>
<button
onClick={() => dispatch(setFilter('completed'))}
className={filter === 'completed' ? 'active' : ''}
>
已完成
</button>
<button onClick={() => dispatch(clearCompleted())}>
清除已完成
</button>
</div>
{/* Todo 列表 */}
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => dispatch(toggleTodo(todo.id))}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => dispatch(deleteTodo(todo.id))}>
删除
</button>
</li>
))}
</ul>
</div>
);
}
export default TodoApp;
3. 更新 Store 配置
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import todoReducer from './todoSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
todos: todoReducer
}
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
异步操作处理
使用 createAsyncThunk
// store/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
// 异步 thunk
export const fetchUser = createAsyncThunk(
'user/fetchUser',
async (userId, { rejectWithValue }) => {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch user');
}
return await response.json();
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const userSlice = createSlice({
name: 'user',
initialState: {
data: null,
loading: false,
error: null
},
reducers: {
clearUser: (state) => {
state.data = null;
state.error = null;
}
},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
});
}
});
export const { clearUser } = userSlice.actions;
export default userSlice.reducer;
在组件中使用异步操作
// components/UserProfile.jsx
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUser, clearUser } from '../store/userSlice';
function UserProfile({ userId }) {
const { data: user, loading, error } = useSelector(state => state.user);
const dispatch = useDispatch();
useEffect(() => {
if (userId) {
dispatch(fetchUser(userId));
}
return () => {
dispatch(clearUser());
};
}, [dispatch, userId]);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
if (!user) return <div>用户不存在</div>;
return (
<div>
<h3>{user.name}</h3>
<p>邮箱: {user.email}</p>
<p>电话: {user.phone}</p>
</div>
);
}
export default UserProfile;
React-Redux Hooks
useSelector
用于从 Redux store 中提取数据
const count = useSelector(state => state.counter.value);
const todos = useSelector(state => state.todos.items);
// 使用 selector 函数
const completedTodos = useSelector(state =>
state.todos.items.filter(todo => todo.completed)
);
useDispatch
用于获取 dispatch 函数
const dispatch = useDispatch();
// 分发 action
dispatch(increment());
dispatch(addTodo('新任务'));
自定义 Hooks(推荐)
// hooks/redux.js
import { useDispatch, useSelector } from 'react-redux';
import type { TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from '../store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
最佳实践
1. 状态规范化
// 好的状态结构
{
users: {
byId: {
1: { id: 1, name: 'John' },
2: { id: 2, name: 'Jane' }
},
allIds: [1, 2]
}
}
// 避免嵌套数组
{
posts: [
{
id: 1,
comments: [/* 大量嵌套数据 */]
}
]
}
2. 使用 Selector
// 创建可复用的 selector
export const selectTodoById = (state, todoId) =>
state.todos.find(todo => todo.id === todoId);
export const selectCompletedTodos = createSelector(
[state => state.todos],
todos => todos.filter(todo => todo.completed)
);
3. 分割 Reducer
// 按功能模块分割
const rootReducer = combineReducers({
auth: authReducer,
posts: postsReducer,
comments: commentsReducer,
ui: uiReducer
});
4. 使用中间件
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}).concat(logger),
});
调试工具
Redux DevTools
const store = configureStore({
reducer: rootReducer,
devTools: process.env.NODE_ENV !== 'production'
});
安装浏览器扩展:Redux DevTools Extension
何时使用 Redux
适合的场景
- 应用状态复杂,多个组件需要共享状态
- 状态更新逻辑复杂
- 需要时间旅行调试
- 需要持久化状态
- 团队协作需要统一的状态管理规范
不适合的场景
- 简单应用,状态简单
- 只有少数组件需要共享状态
- 学习成本超过带来的收益
总结
Redux 为 React 应用提供了强大的状态管理能力。通过遵循单向数据流和不可变更新的原则,Redux 让应用状态变得可预测和易于调试。
现代的 Redux Toolkit 大大简化了使用体验,减少了样板代码,是当前推荐的使用方式。结合 React-Redux 的 hooks API,能够轻松构建可维护的大型应用。
记住:不是所有应用都需要 Redux,选择合适的工具来解决实际问题才是最重要的。
Mobx
MobX是一个简单、可扩展的状态管理库,通过响应式编程让状态管理变得简单和直观。让我为你详细介绍一下。
MobX核心概念
1. 什么是MobX?
MobX通过透明的函数响应式编程使得状态管理变得简单和可扩展。它的核心理念是:任何源自应用状态的东西都应该自动地获得。
2. 核心原理
MobX基于三个重要概念:
- State(状态) :驱动应用的数据
- Derivations(衍生) :从状态中派生出来的任何东西
- Actions(动作) :改变状态的任何一段代码
3. 主要装饰器/API
@observable / makeObservable
将数据变为可观察的:
import { makeObservable, observable, action } from 'mobx';
class TodoStore {
todos = [];
constructor() {
makeObservable(this, {
todos: observable,
addTodo: action
});
}
addTodo = (text) => {
this.todos.push({ text, completed: false });
}
}
@observer
让React组件自动响应状态变化:
import { observer } from 'mobx-react-lite';
const TodoList = observer(() => {
return (
<ul>
{todoStore.todos.map(todo =>
<li key={todo.id}>{todo.text}</li>
)}
</ul>
);
});
@computed / computed
计算属性,基于现有状态自动派生:
class TodoStore {
get completedTodosCount() {
return this.todos.filter(todo => todo.completed).length;
}
}
@action
标记修改状态的方法:
class TodoStore {
toggleTodo = action((id) => {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
});
}
MobX vs Redux
| 特性 | MobX | Redux |
|---|---|---|
| 学习曲线 | 平缓,更直观 | 陡峭,概念较多 |
| 代码量 | 较少 | 较多 |
| 样板代码 | 很少 | 很多 |
| 性能 | 自动优化 | 需要手动优化 |
| 调试 | MobX DevTools | Redux DevTools |
| 时间旅行 | 支持但不是默认 | 原生支持 |
| 函数式编程 | 面向对象 | 函数式 |
使用示例
基础用法
// store.js
import { makeObservable, observable, action, computed } from 'mobx';
class CounterStore {
count = 0;
constructor() {
makeObservable(this, {
count: observable,
increment: action,
decrement: action,
doubleCount: computed
});
}
increment = () => {
this.count++;
}
decrement = () => {
this.count--;
}
get doubleCount() {
return this.count * 2;
}
}
export const counterStore = new CounterStore();
// Counter.jsx
import React from 'react';
import { observer } from 'mobx-react-lite';
import { counterStore } from './store';
const Counter = observer(() => {
return (
<div>
<p>Count: {counterStore.count}</p>
<p>Double: {counterStore.doubleCount}</p>
<button onClick={counterStore.increment}>+</button>
<button onClick={counterStore.decrement}>-</button>
</div>
);
});
export default Counter;