【React】UI库Antd和Redux状态管理

React

流行的UI组件库
创建项目
  • npm create vite antd-demo
安装antd
  • npm install antd --save
redux
  • 官网地址
  • 安装: npm i redux
  • redux是一个专门用于做状态管理的js库
  • 作用:集中式管理react应用中多个组件共享的状态
  • redux的reducer函数必须是一个纯函数
纯函数
  • 不得改写参数数据
  • 不会产生任何副作用,例如网络请求,输入和输出设备
  • 不能调用Date.now()或者Math.random等不纯的方法

什么情况下需要使用redux

  • 某个组件的状态,需要让其它组件可以随时拿到(共享)
  • 一个组件需要改变另一个组的状态(通信)
三个核心概念
action
  • 动作的对象, 包含2个属性,
  • type:标识属性,值为字符串,唯一,必要属性
  • data:数据属性,值类型任意,可选属性
  • 类似{type:'ADD_STUDENT',data:{name:"乞力马扎罗",age:18}}
Store
  • 将state,action,reducer联系在一起的对象
reducer
  • 用于初始化状态,加工状态
  • 加工的时候,根据旧的state和action,产生新的state的纯函数
Redux-V5版本
创建store

redux文件下

  • store.js
javascript 复制代码
import { createStore } from 'redux'; // 如果你用的是官方 redux,否则用之前自定义的 createStore
import { countReducer } from './count';

// 创建 store
const store = createStore(countReducer);

export default store;
对应功能的状态管理
  • count.js
javascript 复制代码
export const countFunAdd = (data) => {
  return {
    type: 'COUNT_ADD',
    data,
  }
}

export const countFunLess = (data) => {
  return {
    type: 'COUNT_LESS',
    data,
  }
}

// 定义 reducer
const initialState = {
  count: 0,
  name: '乞力马扎罗',
}

export const countReducer = (state = initialState, action) => {
  const { type, data } = action
  console.log(state);
  
  switch (type) {
    case 'COUNT_ADD':
      return {
        ...state,
        count: state.count + data,
      }
    case 'COUNT_LESS':
      return {
        ...state,
        count: state.count - data,
      }
    default:
      return state
  }
  
}
页面使用
javascript 复制代码
import React from 'react'
import { Button, Flex } from 'antd'
import { useState, useRef } from 'react'
import store from '../../redux/store'
import { countFunAdd, countFunLess } from '../../redux/count'
function Count() {
  const data = store.getState()
  return (
    <>
      <h1>用户数据:{data.name},{data.count}</h1>
      <button onClick={() => store.dispatch(countFunAdd(1))}>加</button>
      <button onClick={() => store.dispatch(countFunAdd(-1))}>减</button>
    </>
  )
}

export default Count
实时监听
  • main.jsx
javascript 复制代码
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import store from './redux/store'

// 1. 只创建一次 Root 实例(核心优化)
const root = createRoot(document.getElementById('root'));

// 2. 定义渲染函数(复用)
function renderApp() {
  root.render(
    <StrictMode>
      <App />
    </StrictMode>
  );
}
// 3. 初始渲染(必加,保证页面首次加载有内容)
renderApp();

// 4. 监听状态变化,仅重新渲染(复用已创建的 root 实例)
store.subscribe(renderApp);
Redux-v5版本(异步)
  • npm i redux-thunk,并配置在store
  • 异步action,就是指action的值为函数
  • 引入redux-thunk,用于支持异步action,让它当中间件和Store沟通,让只支持对象属性的Store现在支持函数
创建store.js
  • 引入redux-thunk
javascript 复制代码
import { createStore,applyMiddleware } from 'redux' // 如果你用的是官方 redux,否则用之前自定义的 createStore
import { countReducer} from './count'

// 引入redux-thunk,用于支持异步action
import {thunk} from 'redux-thunk'
// 创建 store
const store = createStore(countReducer,applyMiddleware(thunk))

export default store
编写异步action
  • 异步action中一般都会调用同步action
  • 异步中直接使用dispatch
javascript 复制代码
import store from './store'
export const countFunAdd = (data) => {
  return {
    type: 'COUNT_ADD',
    data,
  }
}
// 异步action , 就是指action的值是函数,异步action中一般都会调用同步action
export const countFunAsynAdd = (data) => {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(countFunAdd(data))
    }, 2000)
  }
}
export const countFunLess = (data) => {
  return {
    type: 'COUNT_LESS',
    data,
  }
}

// 定义 reducer
const initialState = {
  count: 0,
  name: '乞力马扎罗',
}

export const countReducer = (state = initialState, action) => {
  const { type, data } = action
  console.log(state)

  switch (type) {
    case 'COUNT_ADD':
      return {
        ...state,
        count: state.count + data,
      }
    case 'COUNT_LESS':
      return {
        ...state,
        count: state.count - data,
      }
    default:
      return state
  }
}
页面实现
javascript 复制代码
import React from 'react'
import { Button, Flex } from 'antd'
import { useState, useRef } from 'react'
import store from '../../redux/store'
import { countFunAdd, countFunLess,countFunAsynAdd} from '../../redux/count'
function Count() {
  const data = store.getState()
  return (
    <>
      <h1>用户数据:{data.name},{data.count}</h1>
      <button onClick={() => store.dispatch(countFunAdd(1))}>加</button>
      <button onClick={() => store.dispatch(countFunAsynAdd(1))}>异步加</button>
      <button onClick={() => store.dispatch(countFunAdd(-1))}>减</button>
    </>
  )
}

export default Count
实时监听
  • main.jsx
javascript 复制代码
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import store from './redux/index'

// 1. 只创建一次 Root 实例(核心优化)
const root = createRoot(document.getElementById('root'));

// 2. 定义渲染函数(复用)
function renderApp() {
  root.render(
    <StrictMode>
      <App />
    </StrictMode>
  );
}
// 3. 初始渲染(必加,保证页面首次加载有内容)
renderApp();

// 4. 监听状态变化,仅重新渲染(复用已创建的 root 实例)
store.subscribe(renderApp);
react-redux模型图
  • UI组件,不能使用任何redux的api,只负责页面的呈现和交互
  • 容器组件,负责和redux通信,将结果交给UI组件,通过react-redux的connect函数创建一个容器组件
  • 注意:容器组件中的store是靠父组件的props传进去的,而不是在容器组件中直接引入的
  • 注意:redux需要通过subscribe监听变化,而react-redux通过connect()()已经实现动态监听,无需subscribe
javascript 复制代码
// 监听状态变化,仅重新渲染(复用已创建的 root 实例)
store.subscribe(renderApp);
文件目录
封装的store和状态数据
  • redux文件下的index.js
javascript 复制代码
import { createStore,applyMiddleware } from 'redux' // 如果你用的是官方 redux,否则用之前自定义的 createStore
import { countReducer} from './count'

// 引入redux-thunk,用于支持异步action
import {thunk} from 'redux-thunk'
// 创建 store
const store = createStore(countReducer,applyMiddleware(thunk))

export default store
  • redux文件下的count.js
javascript 复制代码
export const countFunAdd = (data) => {
  return {
    type: 'COUNT_ADD',
    data,
  }
}
// 异步action , 就是指action的值是函数,异步action中一般都会调用同步action
export const countFunAsynAdd = (data) => {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(countFunAdd(data))
    }, 2000)
  }
}
export const countFunLess = (data) => {
  return {
    type: 'COUNT_LESS',
    data,
  }
}

// 定义 reducer
const initialState = {
  count: 0,
  name: '乞力马扎罗',
}

export const countReducer = (state = initialState, action) => {
  const { type, data } = action
  switch (type) {
    case 'COUNT_ADD':
      return {
        ...state,
        count: state.count + data,
      }
    case 'COUNT_LESS':
      return {
        ...state,
        count: state.count - data,
      }
    default:
      return state
  }
}
容器组件
  • 新建容器组件
  • 和UI组件建立联系
javascript 复制代码
// 容器组件

// 引入Count的UI组件
import CountUI from '../../components/Count'

// 引入connect用于链接UI组件与redux
import { connect } from 'react-redux'

// 引入修改状态数据的封装方法
import { countFunAdd } from '../../redux/count'

//connect传递的函数,第一个函数是state
//a函数返回的对象,这里获取状态state
function a(state) {
  const { name, count } = state
  return state
}

// onnect传递的函数,第二个函数是dispatch
// b 函数的参数只有 dispatch,没有 state
function b(dispatch) {
  return {
    getCount: (params) => {
      dispatch(countFunAdd(params))
    },
  }
}

// 使用connect()()创建并暴露一个Count的容器组件
// connect 是 React-Redux 提供的高阶函数,它的完整参数签名 connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)
// 所以,这里的a,b参数名叫mapStateToProps,mapDispatchToProps更贴切
// CountUI是链接了UI组件
export default connect(a, b)(CountUI)
  • 简写:
UI组件
javascript 复制代码
import React from 'react'
import { Button, Flex } from 'antd'
import { useRef } from 'react'
function Count(props) {
  // UI组件props调用,UI组件里不会出来store,容器组件进行交互
  const { name, count, getName, getCount } = props
  function handleAdd() {
    getCount(1)
  }
  return (
    <>
      <h1>
        用户数据:{name},{count}
      </h1>
      <button
        onClick={handleAdd}
      >
        加
      </button>
      <button onClick={() => {}}>异步加</button>
      <button onClick={() => {}}>减</button>
    </>
  )
}

export default Count
main.jsx
  • 组件顶层,传入store
  • 让react-redux和redux建立联系
javascript 复制代码
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
// 步骤1 Provider包裹
import { Provider } from 'react-redux'
// 步骤2,传入store,让react-redux和redux建立联系
import store from './redux/index.js'
createRoot(document.getElementById('root')).render(
  <StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </StrictMode>
)
页面使用
javascript 复制代码
import React from 'react'
import { Button, Flex } from 'antd'
import { useRef } from 'react'
function Count(props) {
  // UI组件props调用,UI组件里不会出来store,容器组件进行交互
  const { name, count, getName, getCount } = props
  function handleAdd() {
    getCount(1)
  }
  return (
    <>
      <h1>
        用户数据:{name},{count}
      </h1>
      <button
        onClick={handleAdd}
      >
        加
      </button>
      <button onClick={() => {}}>异步加</button>
      <button onClick={() => {}}>减</button>
    </>
  )
}
export default Count
ReduxToolkit(简称RTK),新版本
javascript 复制代码
// 创建一个切片
import { createSlice } from '@reduxjs/toolkit'

// 比较正统的正规的写法
const userSlice = createSlice({
  name: '乞力马扎罗',
  age: '18',
  reducers: {
    //修改名称
    setName(state, action) {
      const { type, payload } = action
      return {
        ...state,
        name: action.payload,
      }
    },
  },
})
export default userSlice
  • 前置安装:npm i react-redux @reduxjs/toolkit
  • 优点:
  • 允许我们在reducers写"可变"逻辑
  • 当然,这个也并不是真正的改变状态值,因为它使用了Immer库
  • 可以检测到"草稿状态"的变化并且基于这些变化生成全新的
  • 类似下面直接修改state,并没有写新的去return覆盖
创建切片
  • redux文件下新建
  • user.js(用户数据)
  • good.js(商品数据)

用户状态数据

javascript 复制代码
//一个项目中有不同的状态数据,根据状态的不同,创建不同的切片,比如用户数据,商品数据
import { createSlice } from '@reduxjs/toolkit'

const userSlice = createSlice({
  // 切片名称
  name: 'user-slice',
  // 初始数据
  initialState: { name: '乞力马扎罗', age: '18' },
  // 操作数据的方法
  reducers: {
    //修改名称
    setName(state, action) {
      state.name = action.payload
    },
    //修改age
    setName(state, action) {
      state.age = action.payload
    },
  },
})
export default userSlice

商品状态数据

javascript 复制代码
import { createSlice } from '@reduxjs/toolkit'

const goodSlice = createSlice({
  name: 'good-slice',
  initialState: {
    name: '衣服',
    price: '200',
  },
  reducers: {
    setName(state, action) {
      state.name = action.payload
    },
    setPrice(state, action) {
      state.price = action.payload
    },
  },
})
合并切片
  • redux文件下新建index.js(合并切片数据)
javascript 复制代码
// 合并切片js

import { configureStore } from '@reduxjs/toolkit'
import goodSlice from './good'
import userSlice from './user'

const store = configureStore({
  reducer: {
    user: userSlice.reducer,
    good: goodSlice.reducer,
  },
})

// 因为setName多个切片都有,实际调用的时候,切片名也会调用{type:'goods-slice/setName',payload:'罗曼蒂克'}
export default store
项目配置
javascript 复制代码
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
//步骤1 Provider包裹
import { Provider } from 'react-redux'
// 步骤2,传入store
import store from './redux/store.js'
createRoot(document.getElementById('root')).render(
  <StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </StrictMode>
)
项目使用
  • useSelector 从 store 中读取数据
  • useDispatch 获取 dispatch 函数,并根据需要 dispatch actions
javascript 复制代码
import React from 'react'
import { Button, Flex } from 'antd'
import { useState, useRef } from 'react'
import { useSelector, useDispatch } from 'react-redux'
function Count() {
  const user = useSelector((state) => state.user)
  const dispatch = useDispatch()
  return (
    <>
      <h1>用户数据:{JSON.stringify(user)}</h1>
      <button
        onClick={() =>
          dispatch({ type: 'user-slice/setName', payload: '罗曼蒂克' })
        }
      >
        点击修改数据
      </button>
    </>
  )
}
export default Count

简约写法

javascript 复制代码
import React from 'react'
import { Button, Flex } from 'antd'
import { useState, useRef } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import userSlice from '../../redux/user'
function Count() {
  const user = useSelector((state) => state.user)
  const dispatch = useDispatch()
  const {setName} = userSlice.actions

  return (
    <>
      <h1>用户数据:{JSON.stringify(user)}</h1>
      {/* 生成 action + 派发 action */}
      <button
        onClick={() =>
          dispatch(setName('罗曼蒂克'))
        }
      >
        点击修改数据
      </button>
    </>
  )
}
export default Count
Provider组件
  • 本质是一个 React 高阶组件,基于 React 的 Context(上下文) 实现
  • 核心作用:将 Redux 的 store 注入到整个 React 组件树中,让所有被 Provider 包裹的子组件(无论层级多深),都能通过 connect 或 useSelector/useDispatch Hooks 访问到 store,无需手动层层传递 store 作为 props
  • 一个应用只需要一个 Provider:通常包裹在根组件外层即可,无需多层嵌套;
  • 支持传入多个 store?不推荐:如果有多个 reducer,建议用 combineReducers 合并成一个 store,而非多个 Provide
  • React-Redux v7+ 推荐 Hooks:除了 connect,也可以用 useSelector/useDispatch 直接获取状态 / 触发 action,底层依然依赖 Provider 提供的 Context
  • 总结:
  • Provider 的核心作用是全局注入 Redux 的 store,基于 React Context 实现
  • 解决了 "手动层层传递 store props" 的痛点,让所有子组件能便捷访问 store
  • 使用时只需在根组件外层包裹一次,传入 store 属性即可

未使用的版本,想要传递store

  • 你需要把 store 作为 props 手动传给每一层组件(比如父→子→孙),层级深了会非常繁琐
javascript 复制代码
import React from 'react'
import Count from "./containers/Count";
import store from './redux/index'

const App = () => {
  return (
    <div>
      {/* 如果不用Provider,就需要给对应的组件传上store */}
      <Count store={store}></Count>
      <Count store={store}></Count>
      <Count store={store}></Count>
      <Count store={store}></Count>
      <Count store={store}></Count>
      <Count store={store}></Count>
    </div>
  )
}

export default App

使用Provider 的话

javascript 复制代码
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
// 步骤1 Provider包裹
import { Provider } from 'react-redux'
// 步骤2,传入store,让react-redux和redux建立联系
import store from './redux/index.js'
createRoot(document.getElementById('root')).render(
  <StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </StrictMode>
)
个人总结
  • 步骤-1:创建store
  • 步骤0:根组件Provider包裹传入store
  • 步骤1:创建页面组件
  • 步骤2:创建动作对象action
  • 步骤3:创建reducer,分发action
  • 步骤4:页面使用redux

传统模式

javascript 复制代码
import React from 'react'
import './index.css'
import { Button, Flex } from 'antd'

// 容器组件
import { connect } from 'react-redux'

// 引入修改状态数据的封装方法
import { countFunAdd } from '../../redux/count'

// 优化mapStateToProps:仅返回组件需要的状态,避免冗余
function mapStateToProps(state) {
  // 只提取 countReduxs 和 personReduxs (组件实际用到的状态)
  return {
    countReduxs: state.countReduxs,
    personReduxs: state.personReduxs
  }
}

// 优化mapDispatchToProps:保持原有逻辑,函数名语义化(原函数b改为mapDispatchToProps)
function mapDispatchToProps(dispatch) {
  return {
    getCount: (params) => {
      dispatch(countFunAdd(params))
    },
  }
}

function Count(props) {
  console.log(props)

  // 移除不存在的 getName 解构,仅保留有效属性
  const { name, count } = props.countReduxs
  const { getCount } = props
  const { personList } = props.personReduxs

  function handleAdd() {
    getCount(1)
  }

  return (
    <div className="Count">
      <h4>
        用户数据:{name},{count}
      </h4>
      {/* 统一使用 antd Button 组件,替换原生 button */}
      <Button onClick={handleAdd} type="primary">加1</Button>
      <h4>
        获取下面的person数据:
        {/* 移除重复的 name/count 渲染,仅保留 personList 遍历 */}
        {personList.map((user, index) => (
          <div key={index} style={{ margin: '10px 0' }}>
            用户名:{user.username},密码:{user.password}
          </div>
        ))}
      </h4>
    </div>
  )
}

// 容器组件导出:使用语义化的映射函数名
export default connect(mapStateToProps, mapDispatchToProps)(Count)

Hooks模式

javascript 复制代码
import React from 'react'
import { Button, Form, Input } from 'antd'
// 1. 引入Redux的useDispatch钩子
import { useDispatch, useSelector } from 'react-redux'
import { personAction } from '../../redux/person'
import './index.css'
const App = () => {
  // 2. 创建dispatch实例(必须在函数组件内部声明)
  const dispatch = useDispatch()
  const count = useSelector((state) => state.countReduxs.count)
  // 3. 重构提交函数,通过dispatch分发action,关键:调用dispatch,把action对象分发到reducer
  const onFinish = (values) => {
    dispatch(personAction(values))
  }

  return (
    <div className="Person">
      <h4>获取上面组件的count:{count}</h4>
      <Form
        name="basic"
        labelCol={{ span: 8 }}
        wrapperCol={{ span: 16 }}
        style={{ maxWidth: 600 }}
        initialValues={{ remember: true }}
        onFinish={onFinish}
        autoComplete="off"
      >
        <Form.Item
          label="Username"
          name="username"
          rules={[{ required: true, message: 'Please input your username!' }]}
        >
          <Input />
        </Form.Item>

        <Form.Item
          label="Password"
          name="password"
          rules={[{ required: true, message: 'Please input your password!' }]}
        >
          <Input.Password />
        </Form.Item>
        <Form.Item label={null}>
          <Button type="primary" htmlType="submit">
            提交
          </Button>
        </Form.Item>
      </Form>
    </div>
  )
}

export default App
redux开发者工具
  • 谷歌商城导入:redux_dev_tools
  • 配合插件使用,npm add redux-devtools-extension
  • redux的store.js或者index.js引入
react 打包
  • pnpm run build
  • 打包完想看,就得放置服务器
  • 想本地启服务器看打包完的dist或者build里的index.html
  • 全局安装npm i serve -g
相关推荐
BlackWolfSky3 小时前
React中文网课程笔记2—实战教程之井字棋游戏
笔记·react.js·游戏
BlackWolfSky3 小时前
React中文网课程笔记1—快速入门
前端·笔记·react.js
借个火er4 小时前
用 Tauri 2.0 + React + Rust 打造跨平台文件工具箱
react.js·rust
码界奇点4 小时前
基于React与TypeScript的后台管理系统设计与实现
前端·c++·react.js·typescript·毕业设计·源代码管理
前端小咸鱼一条5 小时前
Redux
react.js·前端框架
zhenryx5 小时前
React Native 横向滚动指示器组件库(淘宝|京东...&旧版|新版)
javascript·react native·react.js
黎明初时5 小时前
react基础框架搭建4-tailwindcss配置:react+router+redux+axios+Tailwind+webpack
前端·react.js·webpack·前端框架
Mintopia7 小时前
🧠 从零开始:纯手写一个支持流式 JSON 解析的 React Renderer
前端·数据结构·react.js
繁星流动 >_<7 小时前
axure点击图标放大展示
ui·axure