Redux、React Redux 快速学习上手、简单易懂、知识速查

Redux、React Redux 快速学习上手、简单易懂、知识速查

作者GitHub:https://github.com/gitboyzcf 有兴趣可关注!!!

Redux 是什么?

  • 首先理解 "Redux" 是什么?

    Redux 是一个小型的独立 JS 库,它就是一个状态管理工具库,相当于Vue中的Pinia(Vuex)

  • 它有什么作用?
    对整个应用中使用的状态进行集中管理,并可以响应式的更新状态

  • 它帮助我解决什么问题?

    在型应用项目中每个组件的状态随着开发,逐渐增多 ,冗余也会变多,不易维护;当多个组件需要访问相同的数据时

  • 我为什么要使用它?

    跨组件共享状态、处理异步数据流

    需要共享大量状态的组件(如用户信息、主题设置、购物车)

    应用有复杂的状态更新逻辑

    需要跟踪状态变化历史

    大型团队协作,需要一致的状态管理方式

官网
https://www.redux.org.cn/(英)
https://www.redux.org.cn/(中)

执行流程

Redux把整个数据修改的流程分成了三个核心概念:State、Action、Reducer

state:一个对象存放数据的管理状态

action:一个对象用来描述如何修改数据

reducer:一个函数根据action的描述生成一个新的state

体验

demo.html

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button id="decrement">-</button>
  <span id="count">0</span>
  <button id="increment">+</button>

  <script type="module">
    import { createStore } from 'https://cdnjs.cloudflare.com/ajax/libs/redux/5.0.1/redux.legacy-esm.min.js';
    // 1.定义reducer函数
    // 作用:根据不同的action对象,返回不同的新的state  
    // state:管理的数据初始状态
    // action:对象 type 标记当前想要做什么样的修改

    function reducer(state = { count: 0 }, action) {
      // 数据不可变:基于原始状态生成一个新的状态
      switch (action.type) {
        case 'INCREMENT':
          return { count: state.count + 1 }
        case 'DECREMENT':
          return { count: state.count - 1 }
        default:
          return state
      }
    }
    // 2.使用reducer函数生成store实例
    const store = createStore(reducer)
    // 3.通过store实例的subscribe订阅数据变化
    // 回调函数可以在每次state发生变化的时候自动执行
    store.subscribe(() => {
      document.getElementById('count').innerHTML = store.getState().count
      console.log(store.getState())
    })

    // 4.通过store实例的dispatch函数提交action更改状态
    const inBtn = document.getElementById('decrement')
    inBtn.addEventListener('click', () => {
      store.dispatch({ type: 'DECREMENT' })
    })

    const outBtn = document.getElementById('increment')
    outBtn.addEventListener('click', () => {
      store.dispatch({ type: 'INCREMENT' })
    })
  </script>
</body>

</html>

快速上手

术语解析

在我们继续之前,你需要熟悉一些重要的 Redux 术语:

Action

action 是一个具有 type 字段的普通 JavaScript 对象。你可以将 action 视为描述应用程序中发生了什么的事件.

例如体验代码里的 { type: 'DECREMENT' },当然也可以携带数据,请看下面代码:

js 复制代码
const addTodoAction = {
  type: "ADD_TODO",
  payload: "Learn Redux",
};

Reducer

reducer 是一个函数,接收当前的 state 和一个 action 对象,必要时决定如何更新状态,并返回新状态(state)。

例如:

js 复制代码
function reducer(state = { count: 0 }, action) {
  // 数据不可变:基于原始状态生成一个新的状态
  switch (action.type) {
	case 'INCREMENT':
	  return { count: state.count + 1 }
	case 'DECREMENT':
	  return { count: state.count - 1 }
	default:
	  return state
  }
}

Store

当前 Redux 应用的 state 存在于一个名为 store 的对象中

例如 state = { count: 0 }

Dispatch

Redux store 有一个方法叫 dispatch,更新 state 的唯一方法是调用 store.dispatch() 并传入一个 action 对象。

例如 store.dispatch({ type: 'INCREMENT' })

数据流更新动图解析

该图来自redux官网提供

执行流程:

  • 首先创建Store定义自己要管理的state
  • 然后UI界面引用展示
  • 当用户在界面上操作时,比如上图所示添加了一个商品
  • 使用Dispatch根据Action区分用户是什么操作
  • 将新数据和旧数据合并,然后更新Store中的金额state
  • state变化通知UI界面更新操作

React使用Redux

在React中使用redux,官方要求安装俩个其他插件-ReduxToolkit和react-redux

Redux Toolkit 就是Redux的工具库,使其操作Redux更简便

React-Redux 就是React和Redux之间进行关联的工具

结合使用

为了节省时间可以直接使用 vite提供的模版,现成的环境结合使用;当然如果本地有创建好的项目也可以使用
https://stackblitz.com/edit/vitejs-vite-nmchxbgf?file=index.html&terminal=dev

  • 安装插件(在终端界面执行下面命令)

    bash 复制代码
    npm install @reduxjs/toolkit react-redux
  • 创建基本目录结构

  • 写入store/modules/counterStore.ts代码

    ts 复制代码
    import { createSlice } from '@reduxjs/toolkit';
    
    const counterStore = createSlice({
      name: 'counter',
      // 初始化state
      initialState: {
        count: 0,
      },
      // 修改状态的方法 同步方法 支持直接修改
      reducers: {
        increment(state) {
          state.count++;
        },
        decrement(state) {
          state.count--;
        },
        // 第二个参数中的payload属性是在dispatch是调用此方法传的参数
    	 incrementByAmount(state, action: PayloadAction<number>) {
          state.count += action.payload;
        },
      },
    });
    
    //解构出来actionCreater函数
    const { increment, decrement } = counterStore.actions;
    // 获取reducer
    const reducer = counterStore.reducer;
    // 以按需导出的方式导出actionCreater
    export { increment, decrement };
    //以默认导出的方式导出reducer
    export default reducer;
  • 写入store/index.ts代码

    ts 复制代码
    import { configureStore } from '@reduxjs/toolkit';
    
    // 导入子模块reducer
    import counterReducer from './modules/counterStore.ts';
    
    const store = configureStore({
      reducer: {
        counter: counterReducer,
      },
    });
    
    export default store;
  • 在React项目使用react-redux中注入store,修改main.ts

    ts 复制代码
    import { StrictMode } from 'react';
    import { createRoot } from 'react-dom/client';
    import './index.css';
    import App from './App.tsx';
    import store from './store';
    import { Provider } from 'react-redux';
    
    createRoot(document.getElementById('root')!).render(
      <StrictMode>
        <Provider store={store}>
          <App />
        </Provider>
      </StrictMode>
    );
  • 在app.tsx UI 中引入使用

    ts 复制代码
    import { useSelector, useDispatch } from 'react-redux';
    // 导入store中的action对象的方法
    import { decrement, increment } from './store/modules/counterStore.ts';
    
    function App() {
      // 用于获取store中的状态
      const { count } = useSelector((state: any) => state.counter);
    
      // 用于修改store中的状态
      const dispatch = useDispatch();
    
      return (
        <>
          <div className="card">
            <button onClick={() => dispatch(increment())}>+</button>
            {count}
            <button onClick={() => dispatch(decrement())}>-</button>
            {/* 在此传参会放到counterStore中reducers选项中的incrementByAmount方法的action.payload上 */}
            <button onClick={() => dispatch(incrementByAmount(20))}>+20</button>
          </div>
        </>
      );
    }
    
    export default App;
异步更新数据

接着上面示例写

store/modules/counterStore.ts

ts 复制代码
import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
	
const counterStore = createSlice({
  name: 'counter',
  // 初始化state
  initialState: {
	count: 0,
  },
  reducers: {
	// ...
	 incrementByAmount(state, action: PayloadAction<number>) {
      state.count += action.payload;
    },
  },
});

//解构出来actionCreater函数
const { increment, decrement } = counterStore.actions;
const reducer = counterStore.reducer;

// 模拟异步请求
const incrementAsync = (amount: number) => {
	return (dispatch) => {
	    setTimeout(() => {
	      dispatch(incrementByAmount(amount));
	    }, 1000);
	}
};

export { increment, decrement, incrementAsync };
export default reducer;

app.tsx

在当前文件中引入后使用dispatch更新

ts 复制代码
 {/* 异步更新数据 */}![请添加图片描述](https://i-blog.csdnimg.cn/direct/0c6f791d7d304058a061fe9452d2612d.png)

<button onClick={() => dispatch(incrementAsync(5))}>
  +100 Async
</button>

解析异步流程

点击按钮后 dispatch(incrementAsync(5)) 详细执行过程

  1. 第一步:incrementAsync(5) 函数调用

    javascript 复制代码
    dispatch(incrementAsync(5))

    incrementAsync 函数执行

    js 复制代码
    export const incrementAsync = (amount) =>{
    	return  (dispatch) => {
    	  setTimeout(() => {
    	    dispatch(incrementByAmount(amount))
    	  }, 1000)
        }
    }
    • amount 参数被设置为 5
    • 返回一个函数 :(dispatch) => { setTimeout(...) }

    此时 dispatch 接收到的内容

    js 复制代码
    dispatch(  // 接收的是一个函数,而不是普通 action 对象
      (dispatch) => {
        setTimeout(() => {
          dispatch(incrementByAmount(5))
        }, 1000)
      }
    )
  2. 第二步:Redux Thunk 中间件拦截

    普通 dispatch vs Thunk dispatch

    • 普通 action :{ type: 'some-action', payload: data }
    • Thunk action :一个函数 (dispatch, getState) => {}

    Redux 中间件链处理

    javascript 复制代码
    // Redux 中间件检查 dispatch 的内容
    if (typeof action === 'function') {
      // 如果是函数,交给 Redux Thunk 处理
      return action(dispatch, getState, extraArgument)
    } else {
      // 如果是普通 action,继续传递给 reducer
      return next(action)
    }
  3. 第三步:Redux Thunk 执行返回的函数

    函数被立即执行

    javascript 复制代码
    // Redux Thunk 执行:
    (dispatch) => {
      setTimeout(() => {
        dispatch(incrementByAmount(5))  // 1秒后执行
      }, 1000)
    }

    传递 dispatch 参数

    • Redux Thunk 将 store 的 dispatch 函数作为参数传入
    • 现在函数内部可以使用 dispatch 来派发其他 action
  4. 第四步:setTimeout 设置定时器

    异步任务启动

    javascript 复制代码
    setTimeout(() => {
      dispatch(incrementByAmount(5))  // 这部分 1 秒后执行
    }, 1000)
    • 设置一个 1000 毫秒的定时器
    • 当前 dispatch 调用结束,函数返回
    • 主线程继续执行其他代码
  5. 第五步:1 秒后定时器回调执行

    定时器到期

    javascript 复制代码
    dispatch(incrementByAmount(5))

    incrementByAmount 执行流程

    1. incrementByAmount 返回 action 对象:

      javascript 复制代码
      { 
        type: 'counter/incrementByAmount', 
        payload: 5 
      }
    2. Redux 正常处理流程:

      • action 通过中间件链

      • 到达 counterSlice 的 reducer

      • 执行 incrementByAmount reducer 函数:

        javascript 复制代码
        incrementByAmount: (state, action) => {
          state.value += action.payload  // state.value += 5
        }
    3. 状态更新并通知所有订阅的组件

  6. 执行时序总结

    复制代码
    时刻 0ms: dispatch(incrementAsync(5))
        ↓
    时刻 0ms: incrementAsync(5) 返回函数
        ↓
    时刻 0ms: Redux Thunk 拦截并执行函数
        ↓
    时刻 0ms: setTimeout 设置定时器,dispatch 调用结束
        ↓
    时刻 1000ms: 定时器触发
        ↓
    时刻 1000ms: dispatch(incrementByAmount(5))
        ↓
    时刻 1000ms+: 执行 reducer,更新状态,组件重渲染

    关键点:dispatch 调用本身在 0ms 时就完成了,真正的状态更新发生在 1000ms 后。这就是 Redux Thunk 实现异步操作的机制。

源码

https://stackblitz.com/edit/vitejs-vite-zfusdp5l

到这里就结束了,后续还会更新 前端 系列相关,还请持续关注!
感谢阅读,若有错误可以在下方评论区留言哦!!!

推荐文章👇

React、React Router、JSX 简单入门快速上手

相关推荐
wangruofeng9 分钟前
AI Models 1.1.0 发布,查大模型价格、对比模型和找 API 地址
前端·aigc·ai编程
wangruofeng13 分钟前
给前端开发者的 100 个设计参考网站
前端·设计
星栈3 小时前
视频即代码,Remotion 能做什么、不能做什么
前端·音视频开发
广州华水科技3 小时前
单北斗GNSS位移监测系统的应用与技术解析
前端
用户47949283569154 小时前
从一句话或代码仓到可交互 HTML:开源 Agent 画图技能 Archify v3.2 实测与选型边界
前端·后端·面试
风骏时光牛马6 小时前
生成一个AI多模态标题,仅输出一条标题,无额外文字、问句、备选、配图、文案等任何附加内容,不需要其他的任务服务 只要标题。
前端
wangruofeng7 小时前
从 GIF 到 AVIF:网页图片格式演进与 2026 年选型指南
前端·架构
清晨Coding7 小时前
为什么用 React Developer Tools 查看组件树这么乱?
前端·react.js
鶴哥只手遮天8 小时前
《Electron 45.2.3 内核解剖》第 4 章 补丁体系:380 个补丁改了什么
前端·electron
FungLeo10 小时前
成为全栈·Vue 管理后台篇·分类树与标签管理:层级数据如何落到管理界面
前端·javascript·vue.js·分类树·naive ui·标签管理·前端数据结构