redux的基本使用及拆分

本文深入讲解Redux的原理与实践,包括其由来、三大原则、状态管理机制、模块化拆分及容器组件等内容,并探讨Redux在异步操作中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当业务变得复杂,状态也就是数据的管理和传递会变得混乱,redux就是帮助我们管理状态

redux的由来

2013年 Facebook 提出了 Flux 架构的思想,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。如果你的项目组件的数量和层级也变得越来越多,越来越深,此时组件间的数据通信就变得异常的复杂和低效,为了解决这个问题,引入了状态管理(redux)从而很好的解决多组件之间的通信问题。

安装redux-devtools

  • 为了方便调试redux(可选安装),建议去谷歌商店安装redux-devtools

redux的三大原则(理解思考)

  • 单一数据源
    • 整个应用的 state 被储存在一棵对象结构中,并且这个对象结构只存在于唯一一个 store 中
  • State 是只读的
    • redux中的state只读的不可以直接修改
  • 使用纯函数(reducer)来执行修改state
    • 为了修改了state数据,redux定义了一个reducer函数来完成state数据的修改,reducer会接收先前的 state 和 action,并返回新的 state

Redux是将整个应用状态存储到一个地方上称为store,里面保存着一个状态树store tree,组件可以派发(dispatch)行为(action)给store,而不是直接通知其他组件,组件内部通过订阅store中的状态state来刷新自己的视图。
在这里插入图片描述

自己实现一个Redux

基本的状态管理及数据渲染:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Redux principle 01</title>
</head>
<body>
  <h1>redux principle</h1>
  <div class="counter">
    <span class="btn" onclick="dispatch({type: 'COUNT_DECREMENT', number: 10})">-</span>
    <span class="count" id="count"></span>
    <span class="btn" id="add" onclick="dispatch({type: 'COUNT_INCREMENT', number: 10})">+</span>
  </div>
  <script>
    // 定义一个计数器的状态
    const countState = {
      count: 10
    }

    // 定一个方法叫changeState,用于处理state的数据,每次都返回一个新的状态
    const changeState = (action) => {
      switch(action.type) {
        // 处理减
        case 'COUNT_DECREMENT':
          countState.count -= action.number
          break;
        // 处理加        
        case 'COUNT_INCREMENT':
          countState.count += action.number
          break;
        default:
          break;
      }
    }

    // 定义一个方法用于渲染计数器的dom
    const renderCount = (state) => {
      const countDom = document.querySelector('#count')
      countDom.innerHTML = state.count
    }
  
    // 首次渲染数据
    renderCount(countState)

    // 定义一个dispatch的方法,接收到动作之后,自动调用
    const dispatch = (action) => {
      changeState(action)
      renderCount(countState)
    }

  </script>
</body>
</html>

创建createStore方法

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Redux principle 02</title>
</head>
<body>
  <h1>redux principle</h1>
  <div class="counter">
    <span class="btn" onclick="store.dispatch({type: 'COUNT_DECREMENT', number: 10})">-</span>
    <span class="count" id="count"></span>
    <span class="btn" id="add" onclick="store.dispatch({type: 'COUNT_INCREMENT', number: 10})">+</span>
  </div>
  <script>
    // 定义一个方法,用于集中管理state和dispatch
    const createStore = (state, changeState) => {
      // getState用于获取状态
      const getState = () => state
      
      // 定义一个监听器,用于管理一些方法
      const listeners = []
      const subscribe = (listener) => listeners.push(listener)

       // 定义一个dispatch方法,让每次有action传入的时候返回render执行之后的结果
      const dispatch = (action) => {
        // 调用changeState来处理数据
        changeState(state, action)
        // 让监听器里的所以方法运行
        listeners.forEach(listener => listener())
      }
      return {
        getState,
        dispatch,
        subscribe
      }
    }
    // 定义一个计数器的状态
    const countState = {
      count: 10
    }
    // 定一个方法叫changeState,用于处理state的数据,每次都返回一个新的状态
    const changeState = (state, action) => {
      switch(action.type) {
        // 处理减
        case 'COUNT_DECREMENT':
          state.count -= action.number
          break;
        // 处理加        
        case 'COUNT_INCREMENT':
          state.count += action.number
          break;
        default:
          break;
      }
    }

    // 创建一个store
    const store = createStore(countState, changeState)
    // 定义一个方法用于渲染计数器的dom
    const renderCount = () => {
      const countDom = document.querySelector('#count')
      countDom.innerHTML = store.getState().count
    }
    // 初次渲染数据
    renderCount()
    // 监听,只要有dispatch,这个方法就会自动运行
    store.subscribe(renderCount)
  </script>
</body>
</html>

让changeState方法变为一个纯函数

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Redux principle 03</title>
</head>
<body>
  <h1>redux principle</h1>
  <div class="counter">
    <span class="btn" onclick="store.dispatch({type: 'COUNT_DECREMENT', number: 10})">-</span>
    <span class="count" id="count"></span>
    <span class="btn" id="add" onclick="store.dispatch({type: 'COUNT_INCREMENT', number: 10})">+</span>
  </div>
  <script>
    // 定义一个方法,用于集中管理state和dispatch
    const createStore = (state, changeState) => {
      // getState用于获取状态
      const getState = () => state
      
      // 定义一个监听器,用于管理一些方法
      const listeners = []
      const subscribe = (listener) => listeners.push(listener)

      // 定义一个dispatch方法,让每次有action传入的时候返回render执行之后的结果
      const dispatch = (action) => {
        // 调用changeState来处理数据
        state = changeState(state, action)
        // 让监听器里的所有方法运行
        listeners.forEach(listener => listener())
      }
      return {
        getState,
        dispatch,
        subscribe
      }
    }
    // 定义一个计数器的状态
    const countState = {
      count: 10
    }
    // 定一个方法叫changeState,用于处理state的数据,每次都返回一个新的状态
    const changeState = (state, action) => {
      switch(action.type) {
        // 处理减
        case 'COUNT_DECREMENT':
          return {
            ...state,
            count: state.count - action.number
          }
        // 处理加        
        case 'COUNT_INCREMENT':
          return {
            ...state,
            count: state.count + action.number
          }
        default:
          return state
      }
    }

    // 创建一个store
    const store = createStore(countState, changeState)
    // 定义一个方法用于渲染计数器的dom
    const renderCount = () => {
      const countDom = document.querySelector('#count')
      countDom.innerHTML = store.getState().count
    }
    // 初次渲染数据
    renderCount()
    // 监听,只要有dispatch,这个方法就会自动运行
    store.subscribe(renderCount)
  </script>
</body>
</html>

合并state和changeState(最终版)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Redux principle 04</title>
</head>
<body>
  <h1>redux principle</h1>
  <div class="counter">
    <span class="btn" onclick="store.dispatch({type: 'COUNT_DECREMENT', number: 10})">-</span>
    <span class="count" id="count"></span>
    <span class="btn" id="add" onclick="store.dispatch({type: 'COUNT_INCREMENT', number: 10})">+</span>
  </div>
  <script>
    // 定义一个方法,用于集中管理state和dispatch, changeState改名了,专业的叫法是reducer
    const createStore = (reducer) => {
      // 定义一个初始的state
      let state = null
      // getState用于获取状态
      const getState = () => state
      
      // 定义一个监听器,用于管理一些方法
      const listeners = []
      const subscribe = (listener) => listeners.push(listener)

      // 定义一个dispatch方法,让每次有action传入的时候返回reducer执行之后的结果
      const dispatch = (action) => {
        // 调用reducer来处理数据
        state = reducer(state, action)
        // 让监听器里的所有方法运行
        listeners.forEach(listener => listener())
      }
      //  初始化state
      dispatch({})
      return {
        getState,
        dispatch,
        subscribe
      }
    }
    // 定义一个计数器的状态
    const countState = {
      count: 10
    }
    // 定一个方法叫changeState,用于处理state的数据,每次都返回一个新的状态
    const reducer = (state, action) => {
      // 如果state是null, 就返回countState
      if (!state) return countState
      switch(action.type) {
        // 处理减
        case 'COUNT_DECREMENT':
          return {
            ...state,
            count: state.count - action.number
          }
        // 处理加        
        case 'COUNT_INCREMENT':
          return {
            ...state,
            count: state.count + action.number
          }
        default:
          return state
      }
    }

    // 创建一个store
    const store = createStore(reducer)
    // 定义一个方法用于渲染计数器的dom
    const renderCount = () => {
      const countDom = document.querySelector('#count')
      countDom.innerHTML = store.getState().count
    }
    // 初次渲染数据
    renderCount()
    // 监听,只要有dispatch,renderCount就会自动运行
    store.subscribe(renderCount)
  </script>
</body>
</html>

redux的模块化拆分

在这里插入图片描述

  • store通过reducer创建了初始状态

  • view通过store.getState()获取到了store中保存的state挂载在了自己的状态上

  • 用户产生了操作,调用了actions 的方法

  • actions的方法被调用,创建了带有标示性信息的action

  • actions将action通过调用store.dispatch方法发送到了reducer中

  • reducer接收到action并根据标识信息判断之后返回了新的state

  • store的state被reducer更改为新state的时候,store.subscribe方法里的回调函数会执行,此时就可以通知view去重新获取state

Reducer必须是一个纯函数:

Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。Reducer不是只有Redux里才有,之前学的数组方法reduce, 它的第一个参数就是一个reducer

纯函数是函数式编程的概念,必须遵守以下一些约束。

  • 不得改写参数

  • 不能调用系统 I/O 的API

  • 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。

最好把 State 对象设成只读。要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候,与某个 View 对应的 State 总是一个不变(immutable)的对象。

我们可以通过在createStore中传入第二个参数来设置默认的state,但是这种形式只适合于只有一个reducer的时候。

// State 是一个对象
function reducer(state = defaultState, action) {
  return Object.assign({}, state, { thingToChange });
  // 或者
  return { ...state, ...newState };
}

// State 是一个数组
function reducer(state = defaultState, action) {
  return [...state, newItem];
}

划分reducer:

因为一个应用中只能有一个大的state,这样的话reducer中的代码将会特别特别的多,那么就可以使用combineReducers方法将已经分开的reducer合并到一起

注意:

  1. 分离reducer的时候,每一个reducer维护的状态都应该不同
  2. 通过store.getState获取到的数据也是会按照reducers去划分的
  3. 划分多个reducer的时候,默认状态只能创建在reducer中,因为划分reducer的目的,就是为了让每一个reducer都去独立管理一部分状态

容器组件(Smart/Container Components)和展示组件(Dumb/Presentational Components)

展示组件通常会使用无状态组件

展示组件容器组件
作用描述如何展现(骨架、样式)描述如何运行(数据获取、状态更新)
直接使用 Redux
数据来源props监听 Redux state
数据修改从 props 调用回调函数向 Redux 派发 actions
调用方式手动通常由 React Redux 生成

使用react-redux

帮助开发者订阅state数据

可以先结合context来手动连接react和redux。
react-redux提供两个核心的api:

  • Provider: 提供store

  • connect: 用于连接容器组件和展示组件

    • Provider

      根据单一store原则 ,一般只会出现在整个应用程序的最顶层。

    • connect

      语法格式为

      connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)(component)

      一般来说只会用到前面两个,它的作用是:

      • store.getState()的状态转化为展示组件的props
      • actionCreators转化为展示组件props上的方法

只要上层中有Provider组件并且提供了store, 那么,子孙级别的任何组件,要想使用store里的状态,都可以通过connect方法进行连接。如果只是想连接actionCreators,可以第一个参数传递为null

| store

  • | actions
  • | state
  • | reducer
    • | index
  • | methods
  • index.js

index文件

// combineReducers合并多个reducer
// import { createStore, applyMiddleware, compose, combineReducers } from 'redux'
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'

/* // 导入多个reducer
import admin from './reudcer/admin'
import www from './reudcer/www'

// 合并多个reducer,返回一个合并后大的reducer
const reducer = combineReducers({
  // key:value
  // key 访问state前缀 命名空间 key名称随意   state n   state.n  state.名称.n
  // value 单个的reducer函数
  // admin: admin,
  // www: www 
  admin,
  www
}) */
import reducer from './reudcer'

const composeEnhancers = (typeof window !== "undefined" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose
export default createStore(
  reducer,
  composeEnhancers(applyMiddleware(thunk))
)

state

const data = {

}

export default data

reducer

  • 主文件
// combineReducers合并多个reducer
import { combineReducers } from 'redux'

// 导入多个reducer
import admin from './admin'
import web from './web'

// 合并多个reducer,返回一个合并后大的reducer
const reducer = combineReducers({
  // key:value
  // key 访问state前缀 命名空间 key名称随意   state n   state.n  state.名称.n
  // value 单个的reducer函数
  /* admin: admin,
  www: www */
  admin,
  // ....
})

export default reducer
  • 子文件
import method from '../method/adminMethod'
import defaultState from '../state/movie'

const admin = (state = defaultState, action) => {
  try {
    return method[action.type](state, action.data)
  } catch (error) {
    return state
  }
}

export default admin

method

const method = {
  setName(state, data) {
    return {
      ...state,
      name: data
    }
  }
}

export default method

action

export const setName = data => ({
  type: 'setName',
  data
})

Redux异步

通常情况下,action只是一个对象,不能包含异步操作,这导致了很多创建action的逻辑只能写在组件中,代码量较多也不便于复用,同时对该部分代码测试的时候也比较困难,组件的业务逻辑也不清晰,使用中间件了之后,可以通过actionCreator异步编写action,这样代码就会拆分到actionCreator中,可维护性大大提高,可以方便于测试、复用,同时actionCreator还集成了异步操作中不同的action派发机制,减少编码过程中的代码量,思想就是加一层结构可以在过程中做一些事情。
在这里插入图片描述

常见的异步库:

  • Redux-thunk
  • Redux-saga
  • Redux-effects
  • Redux-side-effects
  • Redux-loop
  • Redux-observable

基于Promise的异步库:

  • Redux-promise
  • Redux-promises
  • Redux-simple-promise
  • Redux-promise-middleware

首先只有大型的项目才需要使用到异步库,之所以要使用是为了低耦合,复用我们的异步操作,否则我们可以直接在组件内进行请求,抽离action后,thunk因为写在action内的原因违背了redux的三大原则,因此saga就显得结构更加清晰而已,并非越难就越好,只要能够达到项目要求,使用什么技术都是可以的

saga的解决方案

thunk会需要修改action的内容,让action会产生副作用,saga有点类似于vue的action,将异步的抽离出来,集中处理了所有的异步操作。

action不易维护:

  • action的形式不统一

  • 就是异步操作太为分散,分散在了各个action中

  • 注册插件

import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './root-reducer';
import { watchFetchSaga } from './saga/fetchData.saga';

const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(watchFetchSaga);

...

但是 saga 使用的仍然是普通的 action

// 这个 action 将由 saga 监听,并且出发 side effect 异步加载 api 操作
export const fetchData = () => ({
  type:  "START_FETCH_DATA"
});

// 这个 action 将由 saga 发出
export const fetchSuccess = data => ({
  type: "REMOTE_DATA_RECEIVED",
  payload: data
});

接下来就是注册 saga 相关 side effect 操作。下面的文件是 fetchData.saga.js

import { takeLatest, put } from "redux-saga/effects";

function* fetchDataSaga(action){
  try {
    const response = yield fetch(action.url);
    const data = yield response.json()
    yield put(fetchSuccess(data));
  } catch (error) {
    console.log(error);
  }
}

export default function* watchFetchSaga(){
  // saga 将监听此事件,takeLatest 表示仅仅只监听最新的此事件
  yield takeLatest("START_FETCH_DATA", fetchDataSaga)
}
<think>好的,用户的问题是如何在React项目中集成和使用Redux进行状态管理。我需要根据提供的引用内容来组织回答。首先,我应该先回忆一下Redux基本概念和步骤。根据引用中的信息,安装ReduxReact-Redux是第一步,引用1提到需要安装这两个包,而引用3还提到了Redux Toolkit和redux-persist。用户可能想要一个比较现代的方法,所以应该推荐使用Redux Toolkit,因为它简化了流程。 接下来,创建store是关键步骤。引用2提到Redux Toolkit的configureStore方法,所以应该用这个来创建store。然后需要将store传递给React组件,使用Provider包裹根组件,这部分引用1和引用3都有提到。 然后是创建slice,这里用createSlice来自动生成action creators和reducers,引用2中的例子应该可以用来说明。比如用户模块的例子。处理异步操作的话,可能需要使用createAsyncThunk或者中间件如redux-thunk,引用5解释了redux-thunk的作用,用于处理异步操作,比如登录请求。安装redux-thunk后,需要配置到store中,但引用2提到Redux Toolkit已经默认集成了redux-thunk,可能不需要额外配置? 接下来连接组件,使用useSelector和useDispatch这两个hooks,引用3中的代码示例可以展示如何获取状态和派发action。最后,如果用户需要持久化,可以引入redux-persist,引用3和引用4提到了安装和配置方法,可能需要详细步骤,比如创建persistReducer和persistStore,并用PersistGate包裹应用。 可能还需要注意一些优化点,比如代码结构、中间件的使用、性能优化等。同时,用户可能的问题会涉及性能分析、优化方法或者应用场景,需要准备好相关问题。确保所有步骤都正确引用了对应的引用来源,比如引用1到引用5的相关部分。另外,要检查是否需要处理异步逻辑的具体示例,比如使用createAsyncThunk的例子,或者如何处理错误状态。最后,确保回答结构清晰,分步骤说明,并包含代码示例,让用户能够一步步跟随操作。</think>### 如何在 React 中集成和使用 Redux 进行状态管理 #### 1. **安装依赖** 首先安装 Redux 核心库和 React-Redux 绑定库。若使用现代推荐方式,建议直接使用 **Redux Toolkit(RTK)** 简化流程: ```bash # 使用 npm npm install redux react-redux @reduxjs/toolkit # 使用 yarn yarn add redux react-redux @reduxjs/toolkit # 若需要持久化状态(如保存到本地存储),可添加 redux-persist npm install redux-persist # 或 yarn add redux-persist ``` #### 2. **创建 Redux Store** 使用 RTK 的 `configureStore` 创建全局状态容器: ```javascript // store.js import { configureStore } from '@reduxjs/toolkit'; import userReducer from './features/userSlice'; // 示例模块 export const store = configureStore({ reducer: { user: userReducer, // 其他模块... }, }); ``` #### 3. **将 Store 注入 React 应用** 通过 `Provider` 包裹根组件,使所有子组件能访问状态: ```javascript // index.js import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { store } from './store'; import App from './App'; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') ); ``` #### 4. **定义状态切片(Slice)** 使用 `createSlice` 定义模块化状态和逻辑: ```javascript // features/userSlice.js import { createSlice } from '@reduxjs/toolkit'; const userSlice = createSlice({ name: 'user', initialState: { isLoggedIn: false, token: '' }, reducers: { login: (state, action) => { state.isLoggedIn = true; state.token = action.payload; }, logout: (state) => { state.isLoggedIn = false; state.token = ''; }, }, }); export const { login, logout } = userSlice.actions; export default userSlice.reducer; ``` #### 5. **处理异步操作** 通过 `createAsyncThunk` 或中间件(如 `redux-thunk`)处理异步逻辑: ```javascript // 示例:使用 createAsyncThunk 处理登录请求 import { createAsyncThunk } from '@reduxjs/toolkit'; import api from '../api'; export const loginAsync = createAsyncThunk( 'user/login', async (credentials) => { const response = await api.post('/login', credentials); return response.data.token; } ); // 在 Slice 中处理异步结果 const userSlice = createSlice({ extraReducers: (builder) => { builder.addCase(loginAsync.fulfilled, (state, action) => { state.token = action.payload; }); }, }); ``` #### 6. **在组件中访问状态** 使用 `useSelector` 和 `useDispatch` 连接组件与 Redux: ```javascript // LoginComponent.jsx import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { loginAsync } from '../features/userSlice'; const Login = () => { const dispatch = useDispatch(); const { token } = useSelector((state) => state.user); const handleLogin = () => { dispatch(loginAsync({ username: 'test', password: '123' })); }; return ( <div> {token ? '已登录' : '未登录'} <button onClick={handleLogin}>登录</button> </div> ); }; ``` #### 7. **状态持久化(可选)** 使用 `redux-persist` 持久化状态到本地存储: ```javascript // 配置持久化 import { persistStore, persistReducer } from 'redux-persist'; import storage from 'redux-persist/lib/storage'; const persistConfig = { key: 'root', storage, }; const persistedReducer = persistReducer(persistConfig, rootReducer); export const store = configureStore({ reducer: persistedReducer }); export const persistor = persistStore(store); // 在根组件包裹 PersistGate import { PersistGate } from 'redux-persist/integration/react'; ReactDOM.render( <Provider store={store}> <PersistGate loading={null} persistor={persistor}> <App /> </PersistGate> </Provider>, document.getElementById('root') ); ``` #### 优化建议 1. **模块化代码结构**:按功能拆分 slices,提升可维护性[^3]。 2. **中间件选择**:异步操作推荐使用 `redux-thunk`(默认集成在 RTK 中)或 `redux-saga`[^5]。 3. **性能优化**:使用 `React.memo` 或 `reselect` 避免不必要的渲染[^2]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MaxLoongLvs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值