Skip to main content

Redux

Redux 是一个可预测的状态容器,用于 JavaScript 应用,通常与 React 搭配使用。它帮助你编写行为一致、运行在不同环境的应用。

1. 核心概念

1.1 三大原则

  1. 单一数据源:整个应用的状态存储在单个 store 的对象树中
  2. 状态只读:唯一改变状态的方式是触发 action
  3. 使用纯函数修改:使用 reducer 来描述 action 如何改变状态树

1.2 基本概念

// Action: 描述发生了什么
interface Action {
type: string;
payload?: any;
}

// Reducer: 描述状态如何变化
type Reducer<S, A> = (state: S, action: A) => S;

// Store: 存储状态的容器
type Store = {
getState(): State;
dispatch(action: Action): void;
subscribe(listener: () => void): () => void;
};

2. 基础使用

2.1 创建 Store

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

// 定义初始状态
const initialState = {
counter: 0,
todos: [],
};

// 创建 reducer
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return {
...state,
counter: state.counter + 1,
};
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, action.payload],
};
default:
return state;
}
};

// 创建 store
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);

2.2 Action Creators

// 同步 Action Creator
const increment = () => ({
type: 'INCREMENT',
});

const addTodo = (text: string) => ({
type: 'ADD_TODO',
payload: {
id: Date.now(),
text,
completed: false,
},
});

// 异步 Action Creator (使用 Thunk)
const fetchTodos = () => async (dispatch) => {
try {
dispatch({ type: 'FETCH_TODOS_START' });
const response = await fetch('/api/todos');
const todos = await response.json();
dispatch({ type: 'FETCH_TODOS_SUCCESS', payload: todos });
} catch (error) {
dispatch({ type: 'FETCH_TODOS_ERROR', payload: error.message });
}
};

2.3 在 React 中使用

import { Provider, useSelector, useDispatch } from 'react-redux';

// 提供 store
function App() {
return (
<Provider store={store}>
<Counter />
<TodoList />
</Provider>
);
}

// 使用 hooks 访问状态
function Counter() {
const count = useSelector(state => state.counter);
const dispatch = useDispatch();

return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(increment())}>
Increment
</button>
</div>
);
}

3. 高级特性

3.1 Redux Toolkit

import { createSlice, configureStore } from '@reduxjs/toolkit';

// 创建 slice
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
increment: state => state + 1,
decrement: state => state - 1,
incrementByAmount: (state, action) => state + action.payload,
},
});

// 导出 actions
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// 创建 store
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
});

3.2 中间件

// 自定义日志中间件
const logger = store => next => action => {
console.log('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
return result;
};

// 使用中间件
const store = createStore(
rootReducer,
applyMiddleware(thunk, logger)
);

3.3 Selector 模式

import { createSelector } from 'reselect';

// 基础 selector
const selectTodos = state => state.todos;
const selectFilter = state => state.filter;

// 创建记忆化 selector
const selectVisibleTodos = createSelector(
[selectTodos, selectFilter],
(todos, filter) => {
switch (filter) {
case 'SHOW_COMPLETED':
return todos.filter(todo => todo.completed);
case 'SHOW_ACTIVE':
return todos.filter(todo => !todo.completed);
default:
return todos;
}
}
);

// 在组件中使用
function TodoList() {
const visibleTodos = useSelector(selectVisibleTodos);
return (
<ul>
{visibleTodos.map(todo => (
<TodoItem key={todo.id} {...todo} />
))}
</ul>
);
}

3.4 不可变性更新

// 使用 Immer
import produce from 'immer';

const todosReducer = (state = [], action) => {
return produce(state, draft => {
switch (action.type) {
case 'ADD_TODO':
draft.push(action.payload);
break;
case 'TOGGLE_TODO':
const todo = draft.find(todo => todo.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
break;
}
});
};

4. 最佳实践

4.1 目录结构

src/
├── store/
│ ├── index.ts # store 配置
│ ├── rootReducer.ts # 根 reducer
│ └── features/ # 特性模块
│ ├── todos/
│ │ ├── todosSlice.ts
│ │ ├── selectors.ts
│ │ └── types.ts
│ └── counter/
│ ├── counterSlice.ts
│ └── selectors.ts
├── components/
└── pages/

4.2 TypeScript 支持

// 状态类型
interface RootState {
todos: Todo[];
counter: number;
}

// Action 类型
interface AddTodoAction {
type: 'ADD_TODO';
payload: Todo;
}

interface ToggleTodoAction {
type: 'TOGGLE_TODO';
payload: number;
}

type TodoActions = AddTodoAction | ToggleTodoAction;

// Typed hooks
const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;

4.3 性能优化

  1. 记忆化 Selectors
const selectTodoById = createSelector(
[selectTodos, (_, id) => id],
(todos, id) => todos.find(todo => todo.id === id)
);
  1. 避免不必要的渲染
// 使用 shallowEqual
const selectedData = useSelector(
selectComplexData,
shallowEqual
);
  1. 批量更新
import { batch } from 'react-redux';

batch(() => {
dispatch(action1());
dispatch(action2());
dispatch(action3());
});

5. 调试工具

// Redux DevTools 配置
const store = createStore(
rootReducer,
composeWithDevTools(
applyMiddleware(thunk)
)
);

参考资源