Appearance
Redux / Toolkit 基础使用
第六部分:数据请求与状态管理
Redux 是一个用于管理 JavaScript 应用状态的可预测状态容器。它帮助你编写行为一致、可测试且易于调试的应用。Redux Toolkit 是 Redux 官方推荐的工具集,它简化了 Redux 的使用,提供了一些实用的工具来减少样板代码。
1. Redux 核心概念
1.1 三大原则
- 单一数据源:整个应用的状态被存储在一个单一的 store 中
- 状态是只读的:唯一改变状态的方法是触发一个 action
- 使用纯函数来执行修改:通过 reducers 来描述如何根据 actions 改变状态
1.2 核心概念
- Store:存储应用状态的对象
- Action:描述发生了什么的普通对象
- Reducer:纯函数,接收旧状态和 action,返回新状态
- Dispatch:触发 action 的方法
- Selector:从状态中提取数据的方法
2. Redux Toolkit 安装
bash
# 使用 npm
npm install @reduxjs/toolkit react-redux
# 使用 yarn
yarn add @reduxjs/toolkit react-redux3. 基本使用
3.1 创建 Redux Store
jsx
// src/app/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});3.2 创建 Slice
jsx
// src/features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
value: 0,
status: 'idle',
};
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
// Redux Toolkit 允许我们在 reducers 中直接修改状态
// 这是因为它使用了 Immer 库,它会检测状态的变化并生成新的不可变状态
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
// 导出 actions
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// 导出 reducer
export default counterSlice.reducer;3.3 提供 Store
jsx
// App.js
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './app/store';
import Counter from './features/counter/Counter';
export default function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}3.4 使用 Store
jsx
// src/features/counter/Counter.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './counterSlice';
import { View, Text, Button, StyleSheet } from 'react-native';
export default function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<View style={styles.container}>
<Text style={styles.title}>Redux Counter</Text>
<Text style={styles.count}>{count}</Text>
<View style={styles.buttons}>
<Button
title="-"
onPress={() => dispatch(decrement())}
/>
<Button
title="+"
onPress={() => dispatch(increment())}
/>
</View>
<Button
title="Increment by 5"
onPress={() => dispatch(incrementByAmount(5))}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
},
count: {
fontSize: 48,
marginBottom: 20,
},
buttons: {
flexDirection: 'row',
justifyContent: 'space-around',
width: '50%',
marginBottom: 20,
},
});4. 异步操作
4.1 使用 createAsyncThunk
jsx
// src/features/posts/postsSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
export const fetchPosts = createAsyncThunk(
'posts/fetchPosts',
async () => {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
return response.data;
}
);
const initialState = {
posts: [],
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null,
};
export const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
// 同步 actions
},
extraReducers: (builder) => {
builder
.addCase(fetchPosts.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = 'succeeded';
// 将获取的帖子添加到状态中
state.posts = action.payload;
})
.addCase(fetchPosts.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
},
});
export default postsSlice.reducer;4.2 使用异步操作
jsx
// src/features/posts/PostsList.js
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchPosts } from './postsSlice';
import { View, Text, FlatList, StyleSheet, ActivityIndicator } from 'react-native';
export default function PostsList() {
const dispatch = useDispatch();
const posts = useSelector((state) => state.posts.posts);
const status = useSelector((state) => state.posts.status);
const error = useSelector((state) => state.posts.error);
useEffect(() => {
if (status === 'idle') {
dispatch(fetchPosts());
}
}, [status, dispatch]);
if (status === 'loading') {
return (
<View style={styles.centerContainer}>
<ActivityIndicator size="large" color="#4CAF50" />
</View>
);
}
if (status === 'failed') {
return (
<View style={styles.centerContainer}>
<Text style={styles.errorText}>错误: {error}</Text>
</View>
);
}
return (
<FlatList
data={posts}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={styles.post}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.body}>{item.body}</Text>
</View>
)}
/>
);
}
const styles = StyleSheet.create({
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
errorText: {
fontSize: 16,
color: 'red',
},
post: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
},
body: {
fontSize: 14,
color: '#666',
},
});5. 高级用法
5.1 组合 Reducers
jsx
// src/app/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
import postsReducer from '../features/posts/postsSlice';
import usersReducer from '../features/users/usersSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
posts: postsReducer,
users: usersReducer,
},
});5.2 使用 Selectors
jsx
// src/features/posts/postsSlice.js
import { createSlice, createAsyncThunk, createSelector } from '@reduxjs/toolkit';
import axios from 'axios';
// ... 其他代码
// 创建 selectors
export const selectAllPosts = (state) => state.posts.posts;
export const selectPostById = (state, postId) =>
state.posts.posts.find(post => post.id === postId);
// 创建 memoized selector
export const selectPostsByUserId = createSelector(
[selectAllPosts, (state, userId) => userId],
(posts, userId) => posts.filter(post => post.userId === userId)
);5.3 使用 Selectors
jsx
// src/features/posts/UserPosts.js
import React from 'react';
import { useSelector } from 'react-redux';
import { selectPostsByUserId } from './postsSlice';
import { View, Text, FlatList, StyleSheet } from 'react-native';
const UserPosts = ({ userId }) => {
const userPosts = useSelector((state) => selectPostsByUserId(state, userId));
return (
<View style={styles.container}>
<Text style={styles.title}>用户 {userId} 的帖子</Text>
<FlatList
data={userPosts}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={styles.post}>
<Text style={styles.postTitle}>{item.title}</Text>
</View>
)}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
},
post: {
padding: 10,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
postTitle: {
fontSize: 16,
},
});
export default UserPosts;6. 中间件
6.1 内置中间件
Redux Toolkit 的 configureStore 已经包含了一些常用的中间件:
- redux-thunk:用于处理异步 actions
- redux-devtools-extension:用于调试 Redux
- immutable-state-invariant:在开发模式下检测状态突变
6.2 自定义中间件
jsx
// src/app/middleware/logger.js
const logger = (store) => (next) => (action) => {
console.log('dispatching', action);
const result = next(action);
console.log('next state', store.getState());
return result;
};
export default logger;
// src/app/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
import logger from './middleware/logger';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(logger),
});7. 最佳实践
7.1 目录结构
src/
app/
store.js
middleware/
features/
counter/
counterSlice.js
Counter.js
posts/
postsSlice.js
PostsList.js
users/
usersSlice.js
UsersList.js7.2 命名规范
- Slice 名称:使用小写字母,单词之间用下划线分隔(如
counter_slice) - Action 类型:使用 slice 名称作为前缀(如
counter/increment) - Reducer 函数:使用动词(如
increment,decrement) - Selector 函数:使用
select前缀(如selectAllPosts)
7.3 性能优化
- 使用 memoized selectors:使用
createSelector来缓存计算结果 - 避免不必要的 re-renders:使用
useSelector选择最小的状态片段 - 批量更新:使用
batch来批量处理多个 actions - 异步操作:使用
createAsyncThunk来处理异步操作
7.4 调试技巧
- 使用 Redux DevTools:在浏览器中安装 Redux DevTools 扩展
- 添加日志中间件:在开发模式下添加日志中间件
- 使用 immutable-state-invariant:检测状态突变
8. 常见问题与解决方案
问题 1:状态不更新
问题:dispatch action 后,状态没有更新。
解决方案:
- 检查 reducer 是否正确返回新状态
- 检查是否使用了 mutable 操作(在 Redux Toolkit 中可以直接修改状态)
- 检查 action type 是否正确
问题 2:异步操作失败
问题:异步 action 失败,状态没有正确更新。
解决方案:
- 检查 API 调用是否正确
- 检查
extraReducers是否正确处理了rejected状态 - 检查错误信息是否正确传递
问题 3:组件不重新渲染
问题:状态更新后,组件没有重新渲染。
解决方案:
- 检查
useSelector是否正确选择了状态 - 检查是否使用了 memoized selectors
- 检查组件是否被正确包裹在
Provider中
问题 4:性能问题
问题:Redux 导致应用性能下降。
解决方案:
- 使用 memoized selectors
- 避免在
useSelector中进行复杂计算 - 批量处理多个 actions
- 使用
React.memo来避免不必要的 re-renders
9. 总结
Redux Toolkit 是 Redux 官方推荐的工具集,它简化了 Redux 的使用,提供了一些实用的工具来减少样板代码。通过本文的学习,你应该掌握了以下内容:
- Redux 的核心概念和三大原则
- Redux Toolkit 的基本使用方法
- 如何创建 store、slice 和 actions
- 如何处理异步操作
- 如何使用 selectors 来提取状态
- 如何优化 Redux 应用的性能
- 常见问题的解决方案
在实际开发中,合理使用 Redux Toolkit,可以创建出更加可预测、可测试和可维护的应用状态管理系统。
