本项目是一个在react中,使用 redux 管理状态的基础版实现教程,用简单的案例练习redux的使用,旨在帮助学习 redux 的状态管理机制,包括 store、action、reducer、dispatch 等核心概念。
项目地址:https://github.com/YvetW/redux-mini-demo
注:项目中包含多个版本的代码,有助于理解redux,运行前请先选择需要的版本,修改目录名为src。

版本:
- React v19
 - Redux v5
 - React-Redux v9
 - TypeScript
 - Vite
 
1. 初始化项目
使用 Vite 创建 React+TypeScript 项目:
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
 
主要目录结构
my-app/
├── src/
│   ├── redux/
│   │   ├── action-types.ts  # 定义 Action Types
│   │   ├── actions.ts       # 定义 Actions
│   │   ├── reducers.ts      # 定义 Reducers
│   │   ├── store.ts         # 创建 Store
│   ├── App.tsx              # 组件主入口
│   ├── main.tsx             # 入口文件,渲染 App
│   ├── index.css            # 样式文件
├── package.json             # 依赖管理
├── tsconfig.json            # TypeScript 配置
└── vite.config.ts           # Vite 配置
 
2. 在 React 组件中使用 Redux
本版本不使用 react-redux 或 @reduxjs/toolkit,而是直接使用 Redux 的原生 createStore API(已过时,但有助于理解 Redux 的核心概念)。
2.1 创建 Action Types (action-types.ts)
 
- 在 Redux 中,所有的 
action都应有一个唯一的type,定义type常量。 - 例如:
INCREMENT、DECREMENT、ADD_MESSAGE。 
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_MESSAGE = 'add_message'
 
2.2 创建 Reducers (reducers.ts)
 
- Redux 需要使用 
reducers函数处理state变化。 - 创建两个 
reducer:count和messages。 - 根据不同 
action类型更新state,返回新的state。 
import {combineReducers} from 'redux';
import {ADD_MESSAGE, DECREMENT, INCREMENT} from './action-types.ts';
import {Action} from './actions.ts';
// 管理count
const initCount: number = 0;
function count(state: number = initCount, action: Action): number {
    console.log('count', state, action);
    switch (action.type) {
        case INCREMENT:
            return state + action.data;
        case DECREMENT:
            return state - action.data;
        default:
            return state;
    }
}
// 管理messages
const initMessages: string[] = [];
function messages(state: string[] = initMessages, action: Action): string[] {
    console.log('messages', state, action);
    switch (action.type) {
        case ADD_MESSAGE:
            return [action.data, ...state]
        default:
            return state;
    }
}
export default combineReducers({count, messages});
// 整体state状态结构: {count: 2, messages: ['xx', 'xxx']}
 
2.3 创建 Actions (actions.ts)
 
- Actions 是 Redux store 唯一的数据来源。
 - 创建 
increment、decrement和addMessageaction。 
import {INCREMENT, DECREMENT, ADD_MESSAGE} from './action-types.ts';
// 定义 CountAction 的类型
interface CountAction {
    type: typeof INCREMENT | typeof DECREMENT;
    data: number;
}
// 定义 MessageAction 的类型
interface MessageAction {
    type: typeof ADD_MESSAGE;  // 只有 ADD_MESSAGE 类型
    data: string;  // data 是一个字符串
}
// 联合类型
export type Action = CountAction | MessageAction;
export const increment = (number: number): CountAction => ({type: INCREMENT, data: number});
export const decrement = (number: number): CountAction => ({type: DECREMENT, data: number});
export const add_message = (message: string): MessageAction => ({type: ADD_MESSAGE, data: message});
 
2.4 创建 Store (store.ts)
 
- 通过 
createStore(rootReducer)创建 Reduxstore,集中管理应用状态。 
import { createStore } from 'redux';
import reducers from './reducers'; // 包含多个reducer的reducer
export default createStore(reducers)
 
2.5 在 App.tsx 中使用 store
 
- 通过 
props.store.getState()获取state,控制组件渲染。 - 通过 
props.store.dispatch(action)触发状态更新。 
import {useState} from 'react';
import {Store} from 'redux';
import {Action, increment, decrement, add_message} from './redux/actions.ts';
// 为 App 组件定义一个 Props 类型,这个类型包含 store
interface AppProps {
    store: Store<{
        count: number;
        messages: string[];
    }, Action>;
}
function App({store}: AppProps) {
    const [selectedValue, setSelectedValue] = useState<number>(1);
    const [message, setMessage] = useState<string>('');
    const count = store.getState().count;
    const messages = store.getState().messages;
    function handleIncrement() {
        store.dispatch(increment(selectedValue));
    }
    function handleDecrement() {
        store.dispatch(decrement(selectedValue));
    }
    // 奇数增加
    function handleIncrementIfOdd() {
        // setCount 是异步的,如果 Redux 状态更新了,count 可能不会立即反映出来,直接从 store.getState() 读取最新的 count
        if (store.getState().count % 2 === 1) {
            store.dispatch(increment(selectedValue));
        }
    }
    // 异步增加
    function handleIncrementIfAsync() {
        setTimeout(() => {
            store.dispatch(increment(selectedValue));
        }, 1000);
    }
    function handleAdd() {
        if (message.trim()) {
            store.dispatch(add_message(message));
            setMessage('');
        }
    }
    return (
        <div className="App">
            <div className="count">
                click <span>{count}</span> times
            </div>
            <select
                id="number" value={selectedValue}
                // <option> 的 value 默认是字符串类型(即使它是一个数字),这里强制转换为数字
                onChange={event => setSelectedValue(Number(event.target.value))}
            >
                {[1, 2, 3, 4, 5].map((item) => (
                    <option value={item} key={item}>{item}</option>
                ))}
            </select>
            <button onClick={handleIncrement}>+</button>
            <button onClick={handleDecrement}>-</button>
            <button onClick={handleIncrementIfOdd}>increment if odd</button>
            <button onClick={handleIncrementIfAsync}>increment async</button>
            <hr />
            <input
                type="text"
                value={message}
                onChange={event => setMessage(event.target.value)}
            />
            <button onClick={handleAdd}>add text</button>
            <ul>
                {
                    messages.map((msg, index) => (
                        <li key={index}>{msg}</li>
                    ))
                }
            </ul>
        </div>
    );
}
export default App;
 
2.6 在 main.tsx 中订阅 Store 并触发渲染
 
createRoot()挂载App组件。- 通过 
store.subscribe()监听state变化,每次dispatch触发state变更时重新渲染App。 - React 18 及以上使用 
createRoot()进行渲染,注意避免重复createRoot()调用。 
import {StrictMode} from 'react';
import {createRoot} from 'react-dom/client';
import './index.scss';
import App from './App';
import store from './redux/store.ts';
const root = createRoot(document.getElementById('root')!); // 只调用一次 createRoot()
// root.render() 负责后续更新
function render() {
    root.render(
        <StrictMode>
            <App store={store} />
        </StrictMode>,
    );
}
// 初次渲染
render();
// 订阅 store,state 变化时触发 render
store.subscribe(render);
 
3. 自定义 redux 库
自己定义一个简易的 redux 库,主要针对 createStore 和 combineReducers 的核心概念,帮助理解redux的原理。
简单来说,createStore 用来创建存储和管理应用状态的对象,而 combineReducers 用来将多个子 reducer 合并成一个单一的 reducer。
注:完整版请看项目代码。
3.1 createStore
- 作用:用于创建一个 Redux Store,负责管理应用的状态。
 - 接收的参数: 
  
reducer:根 reducer,决定如何根据action更新state。- 可选的 
preloadedState:初始化时的状态。 
 - 返回的
store对象包含:dispatch:分发action,会触发reducer调用,返回一个新的state,调用所有绑定的listenergetState:得到内部管理的state对象subscribe:监听state的变化,并在状态变化时触发回调。
 
export function createStore<S, A extends Action>(rootReducer: Reducer<S, A>): Store<S> {
    // 内部state,第一次调用reducer得到初始状态并保存
    let state: S;
    // 内部保存n个listener的数组
    const listeners: (() => void)[] = [];
    // 初始调用reducer得到初始state值
    state = rootReducer(undefined, {type: '@mini-redux'} as A);
    // 得到内部管理的state对象
    function getState() {
        return state;
    }
    // 分发action,会触发reducer调用,返回一个新的state,调用所有绑定的listener
    function dispatch(action: Action) {
        // 调用reducer,得到一个新的state,保存
        state = rootReducer(state, action as A);
        // 调用listeners中所有的监视回调函数
        listeners.forEach(listener => listener());
    }
    // 订阅state变化,产生新的状态,也就是dispatch时才执行
    function subscribe(listener: () => void) {
        listeners.push(listener); // 将listener加入到订阅列表
        // 返回一个取消订阅的函数(main.ts 通常不需要手动取消,只执行一次,store.subscribe(render) 只执行一次,不会不断创建新的 listener,所以 不会造成内存泄漏。)
        return () => {
            const index = listeners.indexOf(listener);
            if (index >= 0) {
                listeners.splice(index, 1);
            }
        };
    }
    // 返回一个store对象
    return {getState, dispatch, subscribe};
}
 
3.2 combineReducers
- 作用:将多个子 reducer 组合成一个根 reducer。
 - 接收的参数: 
  
reducers:一个对象,其中包含多个子 reducer,每个子 reducer 负责管理state的一部分。
 - 这个函数返回一个新的 reducer,能够根据 
action调用每个子 reducer,更新对应的state部分。 
// 接收一个包含多个reducer函数的对象,返回一个新的reducer函数
export function combineReducers<S, A extends Action>(
    reducers: { [K in keyof S]: Reducer<S[K], A> }
) {
    return function (state: Partial<S> = {} as S, action: A): S { // 这个 rootReducer 函数会传给 createStore()
        return Object.keys(reducers).reduce((newState, key) => {
            // 确保类型安全:key 是 S 类型的有效键
            newState[key as keyof S] = reducers[key as keyof S](state[key as keyof S], action)
            return newState
        }, {} as S)
    };
}
 
4. react-redux
react-redux 是一个官方推荐的 React 库,它将 Redux 状态管理与 React 组件结合起来,帮助你更方便地在 React 应用中使用 Redux。
4.1 Provider
Provider 是一个 React 组件,它的作用是将 Redux store 传递给整个应用。它让组件树中的所有组件都能访问到 Redux store,因此,Provider 通常包裹在应用的最外层。
const root = createRoot(document.getElementById('root')!);
root.render(
    <StrictMode>
        <Provider store={store}>
            <App />
        </Provider>
    </StrictMode>
);
 
store是你通过createStore创建的 Redux store。Provider会将 Redux store 传递给组件树中的所有组件。
4.2 useSelector 和 useDispatch Hook
 
在旧版 react-redux 中,使用 connect() 高阶组件(HOC)将 Redux 状态和 dispatch 方法注入到组件中。
 在 React-Redux v7.1 及以上版本中,使用 React Hooks 来代替 connect,使函数组件可以直接访问 Redux 状态和 dispatch 方法,使得代码更加简洁:
useSelector:让你从 Redux store 中获取状态数据。useDispatch:让你获取dispatch方法并用于派发action。
示例:
const count = useSelector((state: RootState) => state.count);
    const messages = useSelector((state: RootState) => state.messages);
    
    const dispatch = useDispatch<AppDispatch>();
    function handleIncrement() {
        dispatch(increment(selectedValue));
    }
 
5. redux-toolkit
将前面的redux版本更新为redux-toolkit版本。
5.1 安装 Redux Toolkit
npm install @reduxjs/toolkit
 
5.2 替换 createStore 为 configureStore
 
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import messagesReducer from './messagesSlice';
const store = configureStore({
    reducer: {
        counter: counterReducer,
        messages: messagesReducer
    }
});
export default store;
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
 
configureStore自动启用 Redux DevTools,不需要额外配置。reducer现在是一个对象,每个状态片段(slice)有自己的 reducer。
5.3 使用 createSlice 替换 reducers
 
在 Redux Toolkit(RTK)中,通常会在 redux 目录下创建一个 slices 目录(或 features 目录)来存放 slice 文件,如下所示:
/src
  ├── /redux
  │     ├── /slices
  │     │     ├── counterSlice.ts
  │     │     ├── messagesSlice.ts
  │     ├── store.ts
  │     ├── hooks.ts
 
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
const initialState = { value: 0 };
const counterSlice = createSlice({
    name: 'counter',
    initialState,
    reducers: {
        increment: (state, action: PayloadAction<number>) => {
            state.value += action.payload;
        },
        decrement: (state, action: PayloadAction<number>) => {
            state.value -= action.payload;
        }
    }
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
 
createSlice自动生成action和reducer,不再需要手写switch-case逻辑。- 直接修改 
state,RTK 内部使用Immer处理不可变性。 PayloadAction<number>取代Action里的data。
5.4 替换 actions.ts
 
RTK 版不需要手写 actions,可以直接从 slice 里导出:
import { increment, decrement } from './counterSlice';
// 直接在组件中 dispatch
dispatch(increment(1));
 
5.5 useSelector 和 useDispatch
 
import { useSelector, useDispatch } from 'react-redux';
import { RootState, AppDispatch } from '../redux/store';
import { increment, decrement } from '../redux/counterSlice';
const dispatch = useDispatch<AppDispatch>(); // 需要类型标注
const count = useSelector((state: RootState) => state.counter.value);
dispatch(increment(1));
 
useDispatch<AppDispatch>()让dispatch知道Redux Toolkit的thunk类型。
5.6 处理异步逻辑
以前 Redux 需要 redux-thunk:
export const incrementAsync = (amount: number) => {
    return (dispatch: Dispatch<Action>) => {
        setTimeout(() => {
            dispatch(increment(amount));
        }, 1000);
    };
};
 
RTK 版使用 createAsyncThunk:
import { createAsyncThunk } from '@reduxjs/toolkit';
export const incrementAsync = createAsyncThunk(
    'counter/incrementAsync', // action type
    async (value: number) => {
        // 模拟异步操作
        return new Promise<number>((resolve) => {
            setTimeout(() => {
                resolve(value);
            }, 1000);
        });
    }
);
 
在 slice 里处理异步:
import { createSlice } from '@reduxjs/toolkit';
import { incrementAsync } from './counterThunk';
const counterSlice = createSlice({
    name: 'counter',
    initialState: { count: 0 },
    reducers: {
        increment: (state, action) => { state.count += action.payload; },
        decrement: (state, action) => { state.count -= action.payload; }
    },
    extraReducers: (builder) => {
        builder
            .addCase(incrementAsync.pending, (state) => {
                // 在异步请求开始时可以做一些状态管理
                // 比如:可以设置 loading 状态
            })
            .addCase(incrementAsync.fulfilled, (state, action) => {
                state.count += action.payload;
            })
            .addCase(incrementAsync.rejected, (state, action) => {
                // 可以在这里处理错误
            });
    },
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
 
在组件中你需要使用 dispatch 来派发 incrementAsync 而不是 increment:
  // 直接派发异步增加
    function handleIncrementIfAsync() {
        dispatch(incrementAsync(selectedValue));
    }
 
5.7 迁移 combineReducers
 
以前 Redux 需要 combineReducers,RTK 版直接传 reducer 对象,configureStore 自动调用 combineReducers,不需要手写。
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import messagesReducer from './messagesSlice';
const store = configureStore({
    reducer: {
        counter: counterReducer,
        messages: messagesReducer
    }
});
export default store;
 
总结
- 用 
configureStore代替createStore,自动组合 reducers。 - 用 
createSlice代替reducers.ts,不需要switch-case。 - 不需要手写 
actions.ts,直接用slice.actions。 - 异步逻辑用 
createAsyncThunk代替redux-thunk。 - 自动启用 Redux DevTools 和 
immer,代码更简洁。 
码字不易,欢迎点赞收藏关注!感恩比心~



















