Store / Slice / Reducer
下面,我们来系统的梳理关于 Redux Store / Slice / Reducer 的基本知识点:
一、核心概念概述
1.1 Redux 三大核心元素
- Store:整个应用的状态容器,单一数据源
- Reducer:纯函数,定义状态如何更新
- Slice:Redux Toolkit 引入的概念,整合 reducer 和 actions 的模块化单元
1.2 三者关系
Action → Reducer → Store↑ ↓└── Slice ←─┘
二、Reducer 深度解析
2.1 Reducer 的本质
Reducer 是纯函数,格式:(state, action) => newState
- 输入:当前状态 + action 对象
- 输出:新状态(不可直接修改原状态)
- 特性:无副作用,相同输入总是返回相同输出
2.2 基本 Reducer 结构
const initialState = { value: 0 };function counterReducer(state = initialState, action) {switch (action.type) {case 'counter/increment':return { ...state, value: state.value + 1 };case 'counter/decrement':return { ...state, value: state.value - 1 };case 'counter/add':return { ...state, value: state.value + action.payload };default:return state;}
}
2.3 Reducer 最佳实践
- 不可变性:永远不直接修改 state
- 纯函数:无副作用(API 调用、异步操作等)
- 默认状态:总是提供 initialState
- 默认情况:处理未知 action 时返回原 state
三、Store 深度解析
3.1 Store 的职责
- 保存整个应用状态
- 提供
getState()
方法获取当前状态 - 提供
dispatch(action)
方法更新状态 - 提供
subscribe(listener)
注册状态变化监听器
3.2 创建 Store
传统方式
import { createStore } from 'redux';const store = createStore(counterReducer);
Redux Toolkit 方式
import { configureStore } from '@reduxjs/toolkit';const store = configureStore({reducer: {counter: counterReducer,users: usersReducer}
});
3.3 Store 方法详解
// 获取当前状态
const currentState = store.getState();// 派发 action
store.dispatch({ type: 'counter/increment' });// 订阅状态变化
const unsubscribe = store.subscribe(() => {console.log('State changed:', store.getState());
});// 取消订阅
unsubscribe();
四、Slice 深度解析 (Redux Toolkit)
4.1 什么是 Slice
Slice 是 Redux Toolkit 的核心概念,包含:
- 特定功能模块的初始状态
- 该模块的 reducer 集合
- 自动生成的 action creators
4.2 创建 Slice
import { createSlice } from '@reduxjs/toolkit';const counterSlice = createSlice({name: 'counter', // action 类型前缀initialState: { value: 0 },reducers: {increment: state => {state.value += 1; // 使用 Immer,允许"突变"写法},decrement: state => {state.value -= 1;},add: (state, action) => {state.value += action.payload;},reset: () => ({ value: 0 }) // 返回全新状态},// 可选:处理其他 slice 的 actionextraReducers: (builder) => {builder.addCase(userSlice.actions.login, (state) => {state.value = 0;})}
});export const { increment, decrement, add, reset } = counterSlice.actions;
export default counterSlice.reducer;
4.3 Slice 的优势
- 简化代码:自动生成 action types 和 creators
- 安全"突变":内置 Immer 库,简化不可变更新
- 模块化:按功能组织代码
- TypeScript 友好:自动推导类型
五、完整工作流整合
5.1 配置 Store
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import userReducer from './userSlice';const store = configureStore({reducer: {counter: counterReducer,user: userReducer},// 可选配置middleware: (getDefaultMiddleware) => getDefaultMiddleware(),devTools: process.env.NODE_ENV !== 'production'
});export default store;
5.2 React 组件中使用
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, add } from './counterSlice';function Counter() {const count = useSelector((state) => state.counter.value);const dispatch = useDispatch();return (<div><span>{count}</span><button onClick={() => dispatch(increment())}>+1</button><button onClick={() => dispatch(add(5))}>+5</button></div>);
}
六、高级模式
6.1 异步操作 (createAsyncThunk)
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';export const fetchUser = createAsyncThunk('user/fetchUser', // action 类型前缀async (userId, thunkAPI) => {try {const response = await fetch(`/api/users/${userId}`);return await response.json();} catch (error) {return thunkAPI.rejectWithValue(error.message);}}
);const userSlice = createSlice({name: 'user',initialState: { data: null, status: 'idle', error: null },reducers: {},extraReducers: (builder) => {builder.addCase(fetchUser.pending, (state) => {state.status = 'loading';}).addCase(fetchUser.fulfilled, (state, action) => {state.status = 'succeeded';state.data = action.payload;}).addCase(fetchUser.rejected, (state, action) => {state.status = 'failed';state.error = action.payload;});}
});
6.2 实体适配器 (createEntityAdapter)
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';const todosAdapter = createEntityAdapter();const initialState = todosAdapter.getInitialState({status: 'idle',error: null
});const todosSlice = createSlice({name: 'todos',initialState,reducers: {todoAdded: todosAdapter.addOne,todoUpdated: todosAdapter.updateOne,todoRemoved: todosAdapter.removeOne,todoToggled(state, action) {const todoId = action.payload;const todo = state.entities[todoId];todo.completed = !todo.completed;}},extraReducers: {// 异步处理...}
});// 自动生成选择器
export const { selectAll: selectAllTodos, selectById: selectTodoById } =todosAdapter.getSelectors((state) => state.todos);export const { todoAdded, todoUpdated, todoRemoved, todoToggled } = todosSlice.actions;
export default todosSlice.reducer;
七、实践
7.1 文件结构组织
src/├── app/│ └── store.js├── features/│ ├── counter/│ │ ├── counterSlice.js│ │ └── Counter.js│ ├── todos/│ │ ├── todosSlice.js│ │ └── TodoList.js│ └── users/│ ├── userSlice.js│ └── UserProfile.js└── index.js
7.2 性能优化
- 记忆化选择器:使用
createSelector
import { createSelector } from '@reduxjs/toolkit';const selectTodos = (state) => state.todos.items;export const selectCompletedTodos = createSelector([selectTodos],(todos) => todos.filter(todo => todo.completed)
);
- 组件优化:避免不必要的渲染
// 使用浅比较
import { shallowEqual } from 'react-redux';const user = useSelector(state => state.user, shallowEqual);// 使用 React.memo
const TodoItem = React.memo(({ todo }) => {// ...
});
7.3 测试策略
Reducer 测试:
import counterReducer, { increment, add } from './counterSlice';test('increment action', () => {const previousState = { value: 0 };expect(counterReducer(previousState, increment())).toEqual({ value: 1 });
});test('add action with payload', () => {const previousState = { value: 3 };expect(counterReducer(previousState, add(5))).toEqual({ value: 8 });
});
异步 Thunk 测试:
import { fetchUser } from './userSlice';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';test('fetchUser thunk', async () => {const mock = new MockAdapter(axios);mock.onGet('/api/users/1').reply(200, { id: 1, name: 'John' });const dispatch = jest.fn();const state = {};const thunk = fetchUser(1);await thunk(dispatch, () => state, null);const [pending, fulfilled] = dispatch.mock.calls;expect(pending[0].type).toEqual(fetchUser.pending.type);expect(fulfilled[0].type).toEqual(fetchUser.fulfilled.type);expect(fulfilled[0].payload).toEqual({ id: 1, name: 'John' });
});
八、总结
8.1 核心概念总结
概念 | 职责 | 关键特性 |
---|---|---|
Store | 状态容器 | 单一数据源、dispatch、subscribe |
Reducer | 状态变更 | 纯函数、不可变更新 |
Slice | 模块化管理 | 整合 reducer/action、简化代码 |
8.2 现代 Redux 开发原则
- 使用 Redux Toolkit:简化 Redux 逻辑
- 遵循 Ducks 模式:将相关逻辑放在同一个 slice
- 组件与状态分离:使用容器组件模式
- 异步处理标准化:使用 createAsyncThunk
- 类型安全:结合 TypeScript