【React】React 状态管理与组件通信:Zustand vs Redux📦

组件通信的四大分类

1. 父子组件通信(props + callback)

父组件直接在子组件标签上写属性 ,子组件通过使用props接收数据

🌱 父组件向子组件传数据

jsx 复制代码
// 父组件
function Parent() {
  const [message, setMessage] = useState("Hello from Parent")

  return (
    <div>
      <Child message={message} /> // 
    </div>
  )
}

// 子组件
function Child({ message }) {
  return <p>{message}</p>
}

🔄 子组件向父组件传数据

父组件在子组件标签上绑定函数,子组件调用函数并将变量作为参数传入该函数

jsx 复制代码
// 父组件
function Parent() {
  const handleChildMessage = (childMsg) => {
    alert(`收到子组件的消息:${childMsg}`)
  }

  return (
    <div>
      <Child onSendMessage={handleChildMessage} />
    </div>
  )
}

// 子组件
function Child({ onSendMessage }) {
  return (
    <button onClick={() => onSendMessage("来自子组件!")}>
      发送消息
    </button>
  )
}

2. 兄弟组件通信(父组件作为中转站)

兄弟组件之间通信的关键点在于在父组件共享状态,可以先子传父组件,再父传子组件

jsx 复制代码
function Parent() {
  const [sharedData, setSharedData] = useState("初始数据") // 父组件中设置共享的状态

  return (
    <div>
      <ChildA onSendData={setSharedData} />
      <ChildB sharedData={sharedData} />
    </div>
  )
}

// 兄弟A发送数据
function ChildA({ onSendData }) {
  return (
    <input
      onChange={(e) => onSendData(e.target.value)}
      placeholder="输入数据"
    />
  )
}

// 兄弟B接收数据
function ChildB({ sharedData }) {
  return <p>共享数据:{sharedData}</p>
}

3. 跨层级通信(Context API)

设组件A是组件B的父组件,组件B是组件C的父组件

要将A中的变量传递给C组件,需要创建全局Context组件

  1. 组件A中使用Context.Provider包裹组件B
  2. 在C组件中使用useContext获取Context组件中的变量
jsx 复制代码
import { createContext, useContext, useState } from 'react'

const myContext = createContext() // 创建全局的Context进行数据的传递

function B () { // 孙子组件
  const msg = useContext(myContext) // 获取父组件传递的值
  return (
    <div>
      <h3>Component B --{msg}</h3>
    </div>
  )
}

function A () { // 子组件
  const msg = useContext(myContext) // 获取父组件传递的值
  return (
    <div>
      <h1>Component A --{msg}</h1>
      <B/>
    </div>
  )
}

function Index () { // 父组件
  const [msg, setMsg] = useState('Hello from Grandparents')
  return (
    <div>
      <myContext.Provider value={msg}> 
        <A/>
      </myContext.Provider>
    </div>
  )
}

4. 任意组件通信(全局仓库)

在面对完全没有父子或者层级关系的组件之间进行通信的情况时,上述的三种方法就会显得鸡肋和乏力。如果硬要用这种方法写代码会极其冗杂,难以维护。这个时候就需要全局仓库了。各个组件即用即取,仓库对状态进行统一管理。不需要多余的代码和心智负担。

全局仓库的优势

  1. 解决跨组件通信复杂性

    • 在大型应用中,组件层级深、关系复杂时,直接通过 props 传递状态(Prop Drilling)会导致代码臃肿、难以维护。
    • 全局仓库提供统一的状态访问入口,避免逐层传递状态或回调函数。
  2. 集中管理共享状态

    • 所有组件都可以直接从全局仓库读取或更新状态,避免状态分散在多个组件中,降低耦合度。
  3. 支持复杂业务逻辑

    • 全局仓库可以处理异步操作(如 API 请求)、中间件逻辑(如日志记录、权限控制)等,适合需要复杂状态管理的场景。
  4. 提高可维护性和可测试性

    • 全局状态的变更集中管理,便于调试和单元测试,代码结构更清晰。
  5. 统一状态源

    • 所有组件共享一个真实的状态源,避免数据不一致(如多个组件修改同一状态导致冲突)。

全局的状态管理仓库目前有非常多,不论是官方打造的Redux还是第三方封装的仓库例如Zustandmobxvaltio

笔者偏向使用Zustand,所以后文的分析将围绕ReduxZustand展开


🧩 Zustand 和 Redux 仓库详解 -- 以计数器为例

一、Redux仓库

  1. @reduxjs/toolkit (创建子模块仓库)
  2. 创建一个总仓库,在总仓库中注册各个子模块,子模块通常在modules目录下
  3. react-redux(在根组件中连接仓库和react
  4. 在组件中使用useSelector获取仓库中的数据
  5. 在组件中使用useDispatch获取仓库中的函数
  6. 调用仓库中的方法 得到一个action行为
  7. 调用dispatch函数将action行为派发给仓库

Redux使用时目录结构示例

bash 复制代码
深色版本
src/
├── modules/
│   └── counterSlice.js        # 子模块
├── store.js                   # 总仓库
├── App.jsx                    # 根组件
└── Counter.jsx                # 使用状态的组件

Redux 的经典流程

🛠️ 1. 创建 Slice--创建子模块
jsx 复制代码
// counterSlice.js 步骤 1 -- 创建仓库的子模块:
import { createSlice } from '@reduxjs/toolkit'

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

export const { increment, decrement } = counterSlice.actions
export default counterSlice.reducer
🧱 2. 配置 Store
jsx 复制代码
// store.js 步骤 2: 创建一个总仓库,在总仓库中注册各个子模块
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counterSlice'

const store = configureStore({
  reducer: {
    counter: counterReducer
  }
})

export default store
🔗 3. 在根组件中连接仓库--使仓库全局可用(App.jsx
jsx 复制代码
import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import Counter from './Counter';

const App = () => (
  <Provider store={store}>
    {/* 使用Provider包裹App组件,传入store */}
    <Counter />
  </Provider>
);

export default App;
🧩 4. 组件中使用仓库
jsx 复制代码
// Counter.js
// 步骤 2:连接仓库和`react`
import { useSelector, useDispatch } from 'react-redux'
import { increment, decrement } from './counterSlice'

function Counter() {

  // 步骤 4:使用 useSelector 获取状态
  const count = useSelector(state => state.counter.count)
  
  // 步骤 5:使用 useDispatch 获取 dispatch 函数
  const dispatch = useDispatch()

  // 步骤 6-7:调用 action 并 dispatch
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => dispatch(increment())}>+1</button>
      <button onClick={() => dispatch(decrement())}>-1</button>
    </div>
  )
}

二、Zustand 仓库

  1. zustand(创建状态仓库)
  2. 不需要额外库 (Zustand 天然支持 React,无需像 react-redux
  3. 创建一个总仓库,在总仓库中可以组合多个子模块或功能,通常将子模块放在 storesmodules 目录下
  4. 在组件中使用 useStore 获取仓库中的数据
  5. 在组件中直接调用 useStore 返回的 actions(或方法)来修改状态
  6. 调用仓库中的方法,直接修改状态(Zustand 中不需要 actiondispatch
  7. Zustand 中状态修改是直接的,不需要 dispatch,调用方法即刻更新状态

Zustand 项目目录结构示例

bash 复制代码
src/
├── stores/                  # 所有 Zustand 仓库存放的目录
│   └── counterStore.js      # 示例:计数器模块
├── App.jsx                  # 根组件
└── Counter.jsx              # 使用状态的组件

Zustand 的使用流程

1. 创建状态仓库(核心步骤)

jsx 复制代码
// counterStore.js
import create from 'zustand'

export default const counterStore = create(set => ({
  // 🎯 初始状态
  count: 0,
  // 🛠️ 修改状态的方法
  increment: () => set(state => ({ count: state.count + 1 })),
  decrement: () => set(state => ({ count: state.count - 1 }))
}))

📌 步骤拆解

  1. create() 是 Zustand 的魔法工厂
  2. set 是状态修改的钥匙🔑
  3. 返回对象包含:
    • ✅ 初始状态(count
    • ✅ 修改方法(increment, decrement

2. 组件中使用状态(实战演示)

jsx 复制代码
// Counter.jsx
import { useCounterStore } from '../stores/counterStore.js'

function Counter() {
  // 🧾 获取状态和方法
  const { count, increment, decrement } = useCounterStore()

  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
    </div>
  )
}

💡 小贴士

  • useCounterStore() 直接返回整个 store 的状态和方法
  • 所有组件调用同一个 store 都会自动同步更新✨

🧪 Zustand 与 Redux 对比(重点篇)

1. Zustand 的核心优势

超轻量 API
  • 创建 storecreate(set => ({ ... }))
  • 使用 storeconst useStore = create(...) 直接返回 hook
  • 无中间层 :不需要 Providerconnect,直接调用 hook

3. Zustand vs Redux 对比表

特性 Zustand Redux Toolkit
学习成本 🟢 更简单,适合新手 🔵 需要理解中间件、action、reducer 等概念
API 设计 ✨ 直接使用 hook,无需 Provider 🧩 需要 Provider 和 connect
模块化 🧩 自动模块化,每个 store 独立 🧱 需要手动拆分多个 slice
异步操作 🚀 可直接在 action 中写 async/await ⚙️ 需要配合 createAsyncThunk
性能优化 🚀 自动订阅,无需 createSelector ⚙️ 需要 createSelector 优化选择器
适合场景 🚀 小型项目、快速原型开发 🧱 大型复杂应用

🧠 总结:选择最适合的方案

  • 小型项目 :优先使用 Zustand,代码简洁,上手快
  • 中型项目:Zustand + Context API 可以替代 Redux
  • 大型项目:Redux Toolkit 提供更完善的工具链
  • 跨组件通信:Zustand 的 hook 设计天然支持全局状态共享

通过灵活组合这些工具,可以高效管理复杂应用的状态,同时保持代码的简洁和可维护性!💡

相关推荐
爷_2 小时前
字节跳动震撼开源Coze平台!手把手教你本地搭建AI智能体开发环境
前端·人工智能·后端
charlee443 小时前
行业思考:不是前端不行,是只会前端不行
前端·ai
Amodoro4 小时前
nuxt更改页面渲染的html,去除自定义属性、
前端·html·nuxt3·nuxt2·nuxtjs
Wcowin5 小时前
Mkdocs相关插件推荐(原创+合作)
前端·mkdocs
伍哥的传说5 小时前
CSS+JavaScript 禁用浏览器复制功能的几种方法
前端·javascript·css·vue.js·vue·css3·禁用浏览器复制
lichenyang4535 小时前
Axios封装以及添加拦截器
前端·javascript·react.js·typescript
Trust yourself2436 小时前
想把一个easyui的表格<th>改成下拉怎么做
前端·深度学习·easyui
三口吃掉你6 小时前
Web服务器(Tomcat、项目部署)
服务器·前端·tomcat
Trust yourself2436 小时前
在easyui中如何设置自带的弹窗,有输入框
前端·javascript·easyui
烛阴6 小时前
Tile Pattern
前端·webgl