React(七):Redux

Redux基本使用

纯函数:1.函数内部不能依赖函数外部变量;2.不能产生副作用,在函数内部改变函数外部的变量

React只帮我们解决了DOM的渲染过程,State还是要由我们自己来管理------redux可帮助我们进行管理

Redux三大特点

1.单一数据源:整个应用的状态存储在一个单一的对象树,且该对象树只存储在一个store中

2.State是只读的:状态是不可直接修改的,必须通过触发一个"action"来发起状态的变更

3.使用纯函数来完成状态变更 :通过reducer旧stateactions 联系在一起,并返回一个新的state;不产生任何副作用。

Redux测试项目搭建

step1:项目初始化

复制代码
npm init

step2:安装redux

复制代码
npm i redux

step3:创建store仓库,并存储数据

javascript 复制代码
const { createStore } = require('redux');
const {reducer} = require('./reducer');

// 创建的store
const store = createStore(reducer);

module.exports = store;

step4:创建reducer函数

javascript 复制代码
const { ADD_NUMBER, CHANGE_NAME } = require("./constants")

// 初始化的数据
const initialState = {
  name: "why",
  counter: 100
}

function reducer(state = initialState, action) {
  switch(action.type) {
    case CHANGE_NAME:
      return { ...state, name: action.name }
    case ADD_NUMBER:
      return { ...state, counter: state.counter + action.num }
    default:
      return state
  }
}

module.exports = reducer

注意:在该代码中,我们将action的类型封装到了constants.js文件中,方便复用

javascript 复制代码
const ADD_NUMBER = "add_number"
const CHANGE_NAME = "change_name"

module.exports = {
  ADD_NUMBER,
  CHANGE_NAME
}

step5:通过action来修改state

将它封装到一个单独的文件中

javascript 复制代码
const { ADD_NUMBER, CHANGE_NAME } = require("./constants")

const changeNameAction = (name) => ({
  type: CHANGE_NAME,
  name
})

const addNumberAction = (num) => ({
  type: ADD_NUMBER,
  num
})


module.exports = {
  changeNameAction,
  addNumberAction
}

然后在使用的组件中进行调用

javascript 复制代码
const store = require("./store")
const { addNumberAction, changeNameAction } = require("./store/actionCreators")

const unsubscribe = store.subscribe(() => {
  console.log("订阅数据的变化:", store.getState())
})


// 修改store中的数据: 必须action
store.dispatch(changeNameAction("kobe"))
store.dispatch(changeNameAction("lilei"))
store.dispatch(changeNameAction("james"))

unsubscribe()

// 修改counter
store.dispatch(addNumberAction(10))
store.dispatch(addNumberAction(20))
store.dispatch(addNumberAction(30))

最终,通过拆分代码我们会形成4个文件:

  1. store/index.js文件
  2. store/reducer.js文件
  3. store/actionCreators.js文件
  4. store/constants.js文件

React中使用Redux

redux使用过程:首先会在一个中心的store里面存储我们对应的状态,然后就可以让一些组件中store中订阅一些数据;然后也可以在组件中通过dispatch来派发一些action,这些action就会到达Reducer里面,它就会自动执行该函数,并返回一个新的state对象,再根据新的state来更新数据。数据更新后就会自动告诉订阅者,我现在数据发生改变了,需要拿新数据,界面再重新渲染

step1:创建一个项目并安装redux

step2:创建store

step3:在组件中使用

javascript 复制代码
import React, {PureComponent } from 'react'
import Home from './pages/Home'
import Profile from './pages/profile'
import "./style.css"
import store from './store'

export class App extends PureComponent {
  constructor() {
    super()
    this.state = {
      count: store.getState().count
    }
  }

  componentDidMount() {
    // 订阅store
    store.subscribe(() => {
      const state = store.getState()
      this.setState({
        count: state.count
      })
    })
  }

  render() {
    const {count} = this.state
    return (
      <div>
        <h2>App Count: {count}</h2>
        <div className="pages">
          <Home />
          <Profile />
        </div>
      </div>
    )
  }
}

export default App

React-Redux

redux官方帮助我们提供了 react-redux 库,可简化在react中使用redux的过程

复制代码
npm i react-redux

yarn add react-redux

Provider

在src/index.js中导入Provider,并包裹根组件→将Redux的store传递给整个应用程序→使得所有组件可访问Redux的状态

javascript 复制代码
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import store from './store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

connect

connect是React-redux提供的一个高阶组件(HOC),用于将React组件与Redux的仓库联系起来;

它不会修改原始组件,而是返回一个新的、连接了Redux的组件

作用:

将Redux的状态(state ) 和(dispatch )映射到组件的props

通过 mapStateToPropsmapDispatchToProps,可以自定义组件需要的状态和操作

javascript 复制代码
import axios from "axios"
import * as actionTypes from  "./constants"

export const calcNumber = (num) => {
  return {type:actionTypes.CALC_NUMBER,num}
}
javascript 复制代码
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { calcNumber } from '../store/actionCreators'

export class About extends PureComponent {
  calcNumber(num) {
    this.props.calcNumber(num)
  }
  render() {
    const {count,banners, recommends} = this.props
    return (
      <div>
        <h2>About:{count}</h2>
        <button onClick={e => this.props.calcNumber(6)}>+6</button>
        <button onClick={e => this.props.calcNumber(-6)}>-6</button>
        <button onClick={e => this.props.calcNumber(10)}>+10</button>
        <button onClick={e => this.props.calcNumber(-5)}>-5</button>
        <div className="banners">
          <h2>轮播图数据:</h2>
          <ul>
            {
              banners.map(item => {
                return <li key={item.acm}>
                  {item.title}
                </li>
              })
            }
          </ul>
        </div>
        <div className="recommend">
          <h2>推荐数据</h2>
          <ul>
            {
              recommends.map(item => {
                return <li key={item.acm}>
                  {item.title}
                </li>
              })
            }
          </ul>
        </div>
      </div>
    )
  }
}

function mapStateToProps(state){
  return {
    count: state.count,
    banners:state.banners,
    recommends:state.recommends
  }
}
 
const mapDispatchToProps = (dispatch) => {
  return {
    calcNumber(num) {
      dispatch(calcNumber(num))
    }
  }
}

// connect()返回值是一个高阶组件
export default connect(mapStateToProps,mapDispatchToProps)(About)

组件中进行异步操作

网络请求可以在class组件的componentDidMount中发送

store/home.js

javascript 复制代码
import axios from "axios"
import * as actionTypes from  "./constants"

export const changeBanners = (banners) => {
  return {type:actionTypes.CHANGE_BANNERS,banners}
}

export const changeRecommends = (recommends) => {
  return {type:actionTypes.CHANGE_RECOMMENDS,recommends}
}

Home.jsx

javascript 复制代码
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import axios from 'axios'
import { changeBanners, changeRecommends } from '../store/actionCreators'

export class Category extends PureComponent {
  componentDidMount() {
    axios.get("http://123.207.32.32:8000/home/multidata").then(res => {
      const banners = res.data.data.banner.list
      const recommends = res.data.data.recommend.list
      this.props.changeBanners(banners)
      this.props.changeRecommends(recommends)
    })
  }
  render() {
    return (
      <div>
        <h2>Category Page:</h2>
      </div>
    )
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    changeBanners(banners) {
      dispatch(changeBanners(banners))
    },
    changeRecommends(recommends) {
      dispatch(changeRecommends(recommends))
    }
  }
}

export default connect(null,mapDispatchToProps)(Category)

redux中进行异步操作

网络请求的数据也属于状态管理的一部分,我们最好还是将它放在redux中来管理

在默认情况下,dispatch(action)中传入的是一个对象→可通过reduc-thunk让dispatch中传入一个action函数

step1:安装redux-thunk

复制代码
npm i redux-thunk

step2:在创建store时传入应用了middleware的enhance函数

  • 通过applyMiddleware来结合多个Middleware, 返回一个enhancer;
  • 将enhancer作为第二个参数传入到createStore中;
javascript 复制代码
import { createStore,applyMiddleware } from "redux";
import {thunk} from "redux-thunk"
import reducer from "./reducer";

// 正常情况下,store.dispatch(object) 只能派发一个对象
// 想要派发函数store.dispatch(function) 需要做一个增强
const store = createStore(reducer,applyMiddleware(thunk))

export default store

step3:定义返回一个函数的action

javascript 复制代码
import axios from "axios"
import * as actionTypes from  "./constants"

export const changeBanners = (banners) => {
  return {type:actionTypes.CHANGE_BANNERS,banners}
}

export const changeRecommends = (recommends) => {
  return {type:actionTypes.CHANGE_RECOMMENDS,recommends}
}

export const fetchHomeMultidataAction = () => {
  // 如果是一个普通的action,那么我们需要返回一个对象
  // 问题:对象中不能直接拿到从服务器请求的数据
  // return {}
  
  return function(dispatch,getState) {
    // 异步操作:网络请求
    axios.get("http://123.207.32.32:8000/home/multidata").then(res => {
      const banners = res.data.data.banner.list
      const recommends = res.data.data.recommend.list
      // dispatch({type:actionTypes.CHANGE_BANNERS,banners})
      // dispatch({type:actionTypes.CHANGE_RECOMMENDS,recommends})
      dispatch(changeBanners(banners))
      dispatch(changeRecommends(recommends))
    })
  }
}

step4:在组件中进行派发

javascript 复制代码
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { fetchHomeMultidataAction } from '../store/actionCreators'

export class Category extends PureComponent {
  componentDidMount() {
    this.props.fetchHomeMultidata()
  }
  render() {
    return (
      <div>
        <h2>Category Page:</h2>
      </div>
    )
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    fetchHomeMultidata() {
      dispatch(fetchHomeMultidataAction())
    }
  }
}

export default connect(null,mapDispatchToProps)(Category)

Redux模块拆分

在开发中,如果将所有的数据都存储在一个state中,随着数据越来越多,会难以管理与维护;

我们可以对其进行模块的拆分

combineReducers底层原理

redux给我们提供了一个combineReducers函数可以方便的让我们对多个reducer进行合并

实现原理:

  • 将我们传入的reducers合并到一个对象中,最终返回一个combination函数
  • 在执行combination函数的过程中,会通过判断前后返回的数据是否相同来决定返回之前的state还是新的state
  • 新的state会触发订阅者发生对应的刷新,而旧的state可以有效的组织订阅者发生刷新
javascript 复制代码
// 实现原理
function reducer(state={},action) {
  // 返回一个对象,作为store的状态
  return {
    count:counterReducer(state.count,action),
    home:homeReducer(state.home,action),
    user:userReducer(state.user,action)
  }
}

Redux Toolkit(RTK)

简介

它封装了Redux的核心API,并提供了一些额外工具和约定,帮助我们更高效编写Redux代码

安装:

复制代码
npm install @reduxjs/toolkit react-redux

核心API:

  • configureStore:它自动配置了 Redux 的 createStoreapplyMiddleware,并预装了一些常用的中间件(如 redux-thunkredux-devtools-extension
  • createSlice:接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions
  • createAsyncThunk:接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分派动作类型的 thunk

基本使用

1.configureStore创建大仓库store

javascript 复制代码
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./modules/counter";

const store = configureStore({
  reducer: {
    count: counterReducer,
  },
});

export default store;

2.createSlice创建小仓库

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

const counterSlice = createSlice({
  name: "counter",
  initialState: {
    count: 100
  },
  reducers: {
    increment: (state, action) => {
      state.count += action.payload;
    },
    decrement: (state, action) => {
      state.count -= action.payload;
    }
  }   
})

export const{increment, decrement} = counterSlice.actions
export default counterSlice.reducer;

3.将模块的reducer导入大仓库中

4.在组件中使用数据------还是和之前一样(Provider、connect)

异步使用

1.使用 createAsyncThunk 创建异步操作

  1. createSlice 中处理异步操作的结果
javascript 复制代码
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";

export const fetchHomeMultidataAction = createAsyncThunk(
  "home/multidata", 
  async () => {
  const res = await axios.get("http://123.207.32.32:8000/home/multidata");
  
  // 返回结构,那么action状态就会变成fulfilled状态
  return res.data;
});

const homeSlice = createSlice({
  name: "home",
  initialState: {
    banners: [],
    recommends: []
  },
  reducers: {
    changeBanners(state, { payload }) {
      state.banners = payload;
    },
    changeRecommends(state, { payload }) {
      state.recommends = payload;
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchHomeMultidataAction.pending, (state, action) => {
        console.log("fetchHomeMultidataAction pending");
      })
      .addCase(fetchHomeMultidataAction.fulfilled, (state, action) => {
        console.log("fetchHomeMultidataAction fulfilled");
        // 这里的数据不需要浅拷贝------因为内部的ImmutableJS重构了redux,并返回了一个新对象
        state.banners = action.payload.data.banner.list;
        state.recommends = action.payload.data.recommend.list;
      })
      .addCase(fetchHomeMultidataAction.rejected, (state, action) => {
        console.log("fetchHomeMultidataAction rejected");
      });
  }
});

export const { changeBanners, changeRecommends } = homeSlice.actions;
export default homeSlice.reducer;

3.在组件中调用该函数即可------与原来使用一样

相关推荐
来碗螺狮粉2 小时前
CSR mode下基于react+i18next实践国际化多语言解决方案
react.js
Alang2 小时前
记一次错误使用 useEffect 导致电脑差点“报废”
前端·react.js
关山月3 小时前
🌟 正确管理深层嵌套的 React 组件
react.js
lisw054 小时前
排序算法可视化工具——基于React的交互式应用
算法·react.js·排序算法
__不想说话__4 小时前
面试官问我React Router原理,我掏出了平底锅…
前端·javascript·react.js
DevinJohw4 小时前
为什么我选择[email protected]
react.js·vite
尽-欢7 小时前
以太坊DApp开发脚手架:Scaffold-ETH 2 详细介绍与搭建教程
react.js·typescript·web3·区块链
枫荷10 小时前
彻底理解react中useSyncExternalStore的用法
前端·react.js
全栈派森11 小时前
React Hooks 你知道哪些 ?
前端·react.js
百锦再1 天前
React编程的核心概念:发布-订阅模型、背压与异步非阻塞
前端·javascript·react.js·前端框架·json·ecmascript·html5