Redux
Redux 是一个可预测的状态容器,用于 JavaScript 应用,通常与 React 搭配使用。它帮助你编写行为一致、运行在不同环境的应用。
1. 核心概念
1.1 三大原则
- 单一数据源:整个应用的状态存储在单个 store 的对象树 中
- 状态只读:唯一改变状态的方式是触发 action
- 使用纯函数修改:使用 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 性能优化
- 记忆化 Selectors
const selectTodoById = createSelector(
[selectTodos, (_, id) => id],
(todos, id) => todos.find(todo => todo.id === id)
);
- 避免不必要的渲染
// 使用 shallowEqual
const selectedData = useSelector(
selectComplexData,
shallowEqual
);
- 批量更新
import { batch } from 'react-redux';
batch(() => {
dispatch(action1());
dispatch(action2());
dispatch(action3());
});
5. 调试工具
// Redux DevTools 配置
const store = createStore(
rootReducer,
composeWithDevTools(
applyMiddleware(thunk)
)
);