Redux 对比 Redux Toolkit,一个最小化的对比实践

38 阅读2分钟

Redux

依赖
redux react-redux

Redux 最终项目结构预览:​

/src
  /store
    index.js          # 创建并配置 store
    reducers.js       # 合并所有 reducer
    actions.js        # 集中定义所有 action creators (同步/异步)
  /components
    Counter.js
    TodoList.js
  App.js

reducer部分

第一步:定义同步和异步的 Action Creators

// 同步 Action Creators (返回一个 action 对象)
export const increment = (payload=1) => ({ type: 'COUNTER_INCREMENT',payload });
export const decrement = (payload=1) => ({ type: 'COUNTER_DECREMENT',payload });

export const setTodosLoading = () => ({ type: 'TODOS_LOADING' });
export const addTodo = (text) => ({ type: 'TODOS_ADD', payload: text });

// 异步 Action Creator (返回一个函数)
// thunk 中间件会发现 action 是一个函数,就会执行它,并传入 dispatch 和 getState 作为参数
export const addTodoAsync = (todoText) => {
  return (dispatch) => {
    dispatch(setTodosLoading());
    setTimeout(() => {
      dispatch(addTodo(`来自网络的待办: ${todoText}`));
    }, 1000);
  };
};

第二步:定义reducer,并使用 combineReducers管理复杂状态

// 计数器模块的 reducer
const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case "COUNTER_INCREMENT":
      return { ...state, count: state.count + action.payload };
    case "COUNTER_DECREMENT":
      return { ...state, count: state.count - action.payload };
    default:
      return state;
  }
};

// 待办事项模块的 reducer
const todosReducer = (state = { items: [], isLoading: false }, action) => {
  switch (action.type) {
    case "TODOS_LOADING":
      return { ...state, isLoading: true };
    case "TODOS_ADD":
      return {
        ...state,
        isLoading: false,
        items: [...state.items, { id: Date.now(), text: action.payload }],
      };
    default:
      return state;
  }
};

// 使用 combineReducers 合并多个 reducer
import { combineReducers } from "redux";
const rootReducer = combineReducers({
  counter: counterReducer, 
  todos: todosReducer, 
});

export default rootReducer;

合并后,Redux Store 的状态树结构如下,非常清晰 :

{
  counter: {
    count: 0
  },
  todos: {
    items: [],
    isLoading: false
  }
}

第三步:createStore创建 Store,redux-thunk处理异步

import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers';
import thunk from 'redux-thunk'; // 引入 thunk 中间件

// 使用 applyMiddleware 来增强 store,使其能处理函数类型的 action
const store = createStore(rootReducer, applyMiddleware(thunk));

export default store;

使用部分

计数器组件 (/src/components/Counter.js)​

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from '../store/actions';

function Counter() {
  const count = useSelector(state => state.counter.count);
  const dispatch = useDispatch();

  return (
    <div>
      <h2>计数器: {count}</h2>
      <button onClick={() => dispatch(increment(1))}>+1</button>
      <button onClick={() => dispatch(decrement(1))}>-1</button>
      <button onClick={() => dispatch(increment(5))}>+5</button>
      <button onClick={() => dispatch(decrement(5))}>-5</button>
    </div>
  );
}

export default Counter;

​2. 待办列表组件 (/src/components/TodoList.js)​

import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { addTodoAsync,addTodo } from "../store/actions";

function TodoList() {
  const { items, isLoading } = useSelector((state) => state.todos);
  const dispatch = useDispatch();
  const [inputText, setInputText] = useState("");


  return (
    <div>
      <h2>待办列表</h2>

      <input
        value={inputText}
        onChange={(e) => setInputText(e.target.value)}
        disabled={isLoading}
      />
      <button
        onClick={() => dispatch(addTodo(inputText))}
        type="submit"
        disabled={isLoading}
      >
        添加本地待办
      </button>
      <button
        onClick={() => dispatch(addTodoAsync(inputText))}
        type="submit"
        disabled={isLoading}
      >
        {isLoading ? "添加中..." : "添加异步待办"}
      </button>

      <ul>
        {items.map((todo) => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
}

export default TodoList;

​3. 主应用组件 (/src/App.js)​

import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import Counter from './components/Counter';
import TodoList from './components/TodoList';

function App() {
  return (
    <Provider store={store}>
      <div className="App">
        <h1>Redux最小实践</h1>
        <Counter />
        <TodoList />
      </div>
    </Provider>
  );
}

export default App;

Redux Tookit 改写

依赖
@reduxjs/toolkit react-redux

Redux 最终项目结构预览:​

/src
  /store
     /slice
          counterSlice.js
          todosSlice.js
    index.js          # 创建并配置 store
  /components
    Counter.js
    TodoList.js
  App.js

第一步: 创建 Counter Slice
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { count: 0 },
  reducers: {
    increment: (state,action) => {
      state.count += action.payload; 
    },
    decrement: (state,action) => {
      state.count -= action.payload;
    }
  },
});

export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
第一步: 创建 Todos Slice 含异步逻辑

createAsyncThunk 返回的是一个​​专门用于处理异步逻辑的 thunk action creator 函数, 这个函数被dispatch时会执行异步逻辑,会自动生成pendingfulfilledrejected三种action类型

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


export const fetchTodoAsync = createAsyncThunk(
  'todos/fetchTodo',
  async (todoText) => {
    const response = await new Promise(resolve => 
      setTimeout(() => resolve(`来自网络的待办: ${todoText}`), 1000)
    );
    return response;
  }
);

const todosSlice = createSlice({
  name: 'todos',
  initialState: {
    items: [],
    loading: false
  },
  reducers: {
    addTodo: (state, action) => {
      state.items.push(action.payload);
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchTodoAsync.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchTodoAsync.fulfilled, (state, action) => {
        state.loading = false;
        state.items.push(action.payload);
      })
      .addCase(fetchTodoAsync.rejected, (state, action) => {
        state.loading = false;
      });
  }
});

export const { addTodo } = todosSlice.actions;
export default todosSlice.reducer;

第三步:创建 store

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './slices/counterSlice';
import todosReducer from './slices/todosSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer,
    todos: todosReducer,
  },
});
export default  store

使用部分

计数器组件 (/src/components/Counter.js)​

import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement } from "../store/slices/counterSlice";

function Counter() {
  const count = useSelector((state) => state.counter.count);
  const dispatch = useDispatch();

  return (
    <div>
      <h2>计数器: {count}</h2>
      <button onClick={() => dispatch(increment(1))}>+1</button>
      <button onClick={() => dispatch(decrement(1))}>-1</button>
      <button onClick={() => dispatch(increment(5))}>+5</button>
      <button onClick={() => dispatch(decrement(5))}>-5</button>
    </div>
  );
}

export default Counter;

​2. 待办列表组件 (/src/components/TodoList.js)​

import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { addTodo, fetchTodoAsync } from "../store/slices/todosSlice";

function TodoList() {
  const { items, loading } = useSelector((state) => state.todos);
  const dispatch = useDispatch();
  const [inputText, setInputText] = useState("");

  return (
    <div>
      <h2>待办列表</h2>
      <input
        value={inputText}
        onChange={(e) => setInputText(e.target.value)}
        disabled={loading}
      />
      <button onClick={() => dispatch(addTodo(inputText))}>添加本地待办</button>
      <button
        onClick={() => dispatch(fetchTodoAsync(inputText))}
        disabled={loading}
      >
        {loading ? "添加中..." : "添加异步待办"}
      </button>

      <ul>
        {items.map((todo, index) => (
          <li key={index}>{todo}</li>
        ))}
      </ul>
    </div>
  );
}

export default TodoList;

​3. 主应用组件 (/src/App.js)​

import React from 'react';
import { Provider } from 'react-redux';
import store from './store/index';
import Counter from './components/Counter';
import TodoList from './components/TodoList';

function App() {
  return (
    <Provider store={store}>
      <div className="App">
        <h1>Redux最小实践</h1>
        <Counter />
        <TodoList />
      </div>
    </Provider>
  );
}

export default App;

以下是用 ​​Redux Toolkit (RTK)​​ 对之前传统 Redux 实践进行现代化改造后的完整示例,对比效果显著:

🆚 新旧对比概览

特性传统 ReduxRedux Toolkit (RTK)
​Store 配置​手动 createStoreapplyMiddlewareconfigureStore​ 自动配置中间件 & DevTools 
​Reducer/Action​手写 action types、action creators、switch-casecreateSlice​ 自动生成 actions & reducers 
​异步处理​需额外配置 redux-thunk, 需手动处理 pending/fulfilled/rejected状态​内置 createAsyncThunk​ 自动生成三种状态,extraReducers集中处理,逻辑更清晰
​代码量​冗余(约 20-30 行/功能)​简洁​​(约 5-10 行/功能)