React18状态管理方案之Redux

451 阅读6分钟

什么是Redux

Redux 是一个用于 JavaScript 应用程序的状态管理库,通常与 React 一起使用。它通过提供一个统一的状态存储来管理应用的状态,使得状态的管理和调试变得更加简单和可预测。redux中文文档redux英文文档redux 学习参考文档

Redux 的核心理念是:

  1. 单一数据源:整个应用的状态保存在一个单一的 store 中,方便进行管理和维护。
  2. 状态是只读的:唯一改变状态的方法是派发一个 action,这些 action 描述了发生了什么。
  3. 使用纯函数来修改状态:通过 reducers 来处理 actions,reducers 是纯函数,它接受当前状态和一个 action,返回新的状态。

Redux 提供了一套机制,使得在大型应用中,状态管理变得更加清晰和结构化,非常适合于需要管理复杂状态的应用程序。

可以将 Redux 与 React 或其他视图库一起使用。它体小精悍(只有2kB,包括依赖),却有很强大的插件扩展生态

Redux 核心包做了什么?

Redux 核心包是一个非常小、有意避免主观立场的库。它提供了一些小的 API 原语:

  • createStore 实际创建一个 Redux 存储实例
  • combineReducers 将多个 reducer 函数合并成为一个更大的 reducer
  • applyMiddleware 将多个中间件组合成一个 store 增强器
  • compose 将多个 store 增强器合并成一个单一的 store 增强器

安装

pnpm add redux react-redux @reduxjs/toolkit

TodoList 案例带你一步步熟悉 redux 的基础用法

image.png

  • "@reduxjs/toolkit": "^2.3.0"
  • "antd": "^5.21.1"
  • "react": "^18.3.1"
  • "react-dom": "^18.3.1"
  • "react-redux": "^9.1.2"
  • "redux": "^5.0.1"
  • "typescript": "^5.6.3"

创建 Redux Slice

// todo.ts 
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

interface Todo {
  id: number
  text: string
}

interface TodoState {
  todos: Todo[]
}

const initialState: TodoState = {
  todos: [],
}

const todoSlice = createSlice({
  name: 'todos',
  initialState,
  reducers: {
    addTodo: (state, action: PayloadAction<string>) => {
      const newTodo: Todo = { id: Date.now(), text: action.payload }
      state.todos.push(newTodo)
    },
    removeTodo: (state, action: PayloadAction<number>) => {
      state.todos = state.todos.filter((todo) => todo.id !== action.payload)
    },
  },
})

export const { addTodo, removeTodo } = todoSlice.actions
export default todoSlice.reducer

创建 Redux Store

// store.ts
import { configureStore } from '@reduxjs/toolkit'
import todoReducer from './todoSlice'

const store = configureStore({
  reducer: {
    todos: todoReducer,
  },
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

export default store

设置 Provider

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux';
import store from './store';
import App from './App.tsx'
import './index.css'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <Provider store={store}>
    <App />
  </Provider>
  </StrictMode>,
)

 创建 TodoList 组件

import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { addTodo, removeTodo } from './todo';
import { Button, Input, List } from 'antd';

const TodoList: React.FC = () => {
    const [inputValue, setInputValue] = useState('');
    const dispatch = useDispatch();
    const todos = useSelector((state: any) => state.todos.todos);

    const handleAddTodo = () => {
        if (inputValue.trim()) {
            dispatch(addTodo(inputValue));
            setInputValue('');
        }
    };

    const handleRemoveTodo = (id: number) => {
        dispatch(removeTodo(id));
    };

    return (
        <div style={{ padding: '20px' }}>
            <Input
                value={inputValue}
                onChange={e => setInputValue(e.target.value)}
                placeholder="添加新的 todo"
                style={{ width: '300px', marginRight: '10px' }}
            />
            <Button type="primary" onClick={handleAddTodo}>添加</Button>
            <List
                style={{ marginTop: '20px' }}
                bordered
                dataSource={todos}
                renderItem={item => (
                    <List.Item
                        actions={[<Button type="link" onClick={() => handleRemoveTodo(item.id)}>删除</Button>]}
                    >
                        {item.text}
                    </List.Item>
                )}
            />
        </div>
    );
};

export default TodoList;

在 App 中使用 TodoList

import React from 'react'
import TodoList from './TodoList'

const App: React.FC = () => {
  return (
    <div>
      <h1 style={{ textAlign: 'center' }}>Todo List</h1>
      <TodoList />
    </div>
  )
}

export default App

当然你也可以不使用 @reduxjs/toolkit 的 Redux 应用程序。通过手动创建 reducer、store 和 actions但是不推荐这么做下面会解释以及你也可以对应代码的简洁程度,只需要更改以下几步就是以前你熟悉的用法了尤其是现有老项目(其他的和上面保持一致即可)

 创建 Reducer

// todo.ts
interface TodoItem {
  id: number
  text: string
}

interface TodoState {
  todos: TodoItem[]
}

const initialState: TodoState = {
  todos: [],
}

// 创建 Reducer
const todoReducer = (state = initialState, action: any) => {
  switch (action.type) {
    case 'ADD_TODO':
      const newTodo: TodoItem = { id: Date.now(), text: action.payload }
      return { ...state, todos: [...state.todos, newTodo] }

    case 'REMOVE_TODO':
      return {
        ...state,
        todos: state.todos.filter((todo) => todo.id !== action.payload),
      }

    default:
      return state
  }
}

export default todoReducer

创建 Store

会直接提醒你,但是一样能正常使用,就是不推荐这样使用了(传统用法) image.png

// store.ts
import { createStore } from 'redux';
import todoReducer from './todo';

const store = createStore(todoReducer);

export default store;

创建 TodoList 组件

import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Button, Input, List } from 'antd';

interface Todo {
  id: number; // 或者 string
  text: string;
}

const TodoList: React.FC = () => {
    const [inputValue, setInputValue] = useState('');
    const dispatch = useDispatch();
    const todos = useSelector((state: any) => state.todos);

    const handleAddTodo = () => {
        if (inputValue.trim()) {
            dispatch({ type: 'ADD_TODO', payload: inputValue });
            setInputValue('');
        }
    };

    const handleRemoveTodo = (id: number) => {
        dispatch({ type: 'REMOVE_TODO', payload: id });
    };

    return (
        <div style={{ padding: '20px' }}>
            <Input
                value={inputValue}
                onChange={e => setInputValue(e.target.value)}
                placeholder="添加新的 todo"
                style={{ width: '300px', marginRight: '10px' }}
            />
            <Button type="primary" onClick={handleAddTodo}>添加</Button>
            <List
                style={{ marginTop: '20px' }}
                bordered
                dataSource={todos}
                renderItem={(item:Todo) => (
                    <List.Item
                        actions={[<Button type="link" onClick={() => handleRemoveTodo(item.id)}>删除</Button>]}
                    >
                        {item.text}
                    </List.Item>
                )}
            />
        </div>
    );
};

export default TodoList;

一样可以达到同样的效果,只是不推荐这样的方式处理了目前~~~~与时俱进吧!!!

为什么 Redux Toolkit 是如今使用 Redux 的方式

推荐使用 Redux Toolkit !!!

什么是 Redux Toolkit?

官方原话:Redux Toolkit (也称为  "RTK"  ) 是我们官方推荐的编写 Redux 逻辑的方法。@reduxjs/toolkit 包封装了核心的 redux 包,包含我们认为构建 Redux 应用所必须的 API 方法和常用依赖。 Redux Toolkit 集成了我们建议的最佳实践,简化了大部分 Redux 任务,阻止了常见错误,并让编写 Redux 应用程序变得更容易。

如果今天你要写任何的 Redux 逻辑,你都应该使用 Redux Toolkit 来编写代码

RTK 包括一些实用程序,可以帮助简化许多常见的用例,包括 配置 Redux store、 创建 reducer 函数并使用不可变更新逻辑 和 一次性创建状态的某个"片段"(slice)

无论你是刚接触 Redux 的新用户正在设计你的第一个项目,还是已有经验的用户想简化一个现有的应用,Redux Toolkit 都能够帮助你写出更好的 Redux 代码。

redux的传统用法或者叫老用法(不使用Redux Toolkit)和现在的新用法(使用Redux Toolkit)对比

1. 安装和配置

  • 老用法:

    • 需要手动安装 Redux 和 react-redux。
    • 创建 Redux store 时,需要明确地使用 createStore 和中间件,如 applyMiddleware
    import { createStore, applyMiddleware } from 'redux';
    import { Provider } from 'react-redux';
    import thunk from 'redux-thunk';
    import rootReducer from './reducers';
    
    const store = createStore(rootReducer, applyMiddleware(thunk));
    
  • 新用法(Redux Toolkit) :

    • 通过 @reduxjs/toolkit 包简化配置,使用 configureStore
    import { configureStore } from '@reduxjs/toolkit';
    import { Provider } from 'react-redux';
    import rootReducer from './reducers';
    
    const store = configureStore({
        reducer: rootReducer
    });
    

2. Reducers 和 Actions

  • 老用法:

    • 状态管理和动作分离,手动定义动作类型和创建动作生成器。
    • 使用 switch 语句处理 actions。
    const ADD_TODO = 'ADD_TODO';
    
    const addTodo = (text) => ({
        type: ADD_TODO,
        payload: text,
    });
    
    const todosReducer = (state = [], action) => {
        switch (action.type) {
            case ADD_TODO:
                return [...state, action.payload];
            default:
                return state;
        }
    };
    
  • 新用法(Redux Toolkit) :

    • 使用 createSlice 来自动生成 reducers 和 actions。
    • 无需手动创建动作类型和处理逻辑,结构更清晰。
    import { createSlice } from '@reduxjs/toolkit';
    
    const todosSlice = createSlice({
        name: 'todos',
        initialState: [],
        reducers: {
            addTodo: (state, action) => {
                state.push(action.payload);
            },
        },
    });
    
    export const { addTodo } = todosSlice.actions;
    export default todosSlice.reducer;
    

3. 中间件

  • 老用法:

    • 手动配置中间件,并使用 applyMiddleware 来应用。
  • 新用法(Redux Toolkit) :

    • 默认内置了一些常用的中间件(如 redux-thunk),同时支持自定义中间件的添加。

4. 状态不可变性

  • 老用法:

    • 开发者需要手动处理状态的不可变性,使用 Object.assign 或扩展运算符等。
  • 新用法(Redux Toolkit) :

    • 使用 Immer 库,使得状态更新变得简单和直观,开发者可以直接修改状态,内部会处理不可变性。

5. 性能和开发体验

  • 老用法:

    • 开发流程较为繁琐,需要更多的样板代码。
  • 新用法(Redux Toolkit) :

    • 简化了 Redux 的配置和使用,降低了入门门槛,提高了开发体验和效率。
    • 强调了最佳实践,比如使用 createSlice 和 configureStore

总结

Redux Toolkit 通过简化配置、自动生成 reducers 和 actions、支持直接修改状态 (通过 Immer),以及内置中间件等特性,极大地提升了 Redux 的使用体验。因此,对于新项目,推荐使用 Redux Toolkit,而对于已有的老项目,可以逐步迁移到 Redux Toolkit 以获得更好的开发效率。好了,关于redux的用法就介绍到这里吧,剩下的自己琢磨官方api的差异性吧,以实践、思考、总结为主。。。