Skip to content

Redux / Toolkit 基础使用

第六部分:数据请求与状态管理

Redux 是一个用于管理 JavaScript 应用状态的可预测状态容器。它帮助你编写行为一致、可测试且易于调试的应用。Redux Toolkit 是 Redux 官方推荐的工具集,它简化了 Redux 的使用,提供了一些实用的工具来减少样板代码。

1. Redux 核心概念

1.1 三大原则

  1. 单一数据源:整个应用的状态被存储在一个单一的 store 中
  2. 状态是只读的:唯一改变状态的方法是触发一个 action
  3. 使用纯函数来执行修改:通过 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-redux

3. 基本使用

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.js

7.2 命名规范

  • Slice 名称:使用小写字母,单词之间用下划线分隔(如 counter_slice
  • Action 类型:使用 slice 名称作为前缀(如 counter/increment
  • Reducer 函数:使用动词(如 increment, decrement
  • Selector 函数:使用 select 前缀(如 selectAllPosts

7.3 性能优化

  1. 使用 memoized selectors:使用 createSelector 来缓存计算结果
  2. 避免不必要的 re-renders:使用 useSelector 选择最小的状态片段
  3. 批量更新:使用 batch 来批量处理多个 actions
  4. 异步操作:使用 createAsyncThunk 来处理异步操作

7.4 调试技巧

  1. 使用 Redux DevTools:在浏览器中安装 Redux DevTools 扩展
  2. 添加日志中间件:在开发模式下添加日志中间件
  3. 使用 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 的使用,提供了一些实用的工具来减少样板代码。通过本文的学习,你应该掌握了以下内容:

  1. Redux 的核心概念和三大原则
  2. Redux Toolkit 的基本使用方法
  3. 如何创建 store、slice 和 actions
  4. 如何处理异步操作
  5. 如何使用 selectors 来提取状态
  6. 如何优化 Redux 应用的性能
  7. 常见问题的解决方案

在实际开发中,合理使用 Redux Toolkit,可以创建出更加可预测、可测试和可维护的应用状态管理系统。

© 2026 编程马·菜鸟教程 版权所有