Redux & Redux Toolkit

156 阅读4分钟

一、安装RTK相关包和开发工具

// 安装 @reduxjs/toolkit 和 react-redux
npm install @reduxjs/toolkit react-redux

@reduxjs/toolkit 封装了最开始redux的一些逻辑而 react-redux 可以使用Provider和connect来将仓库里的state和dispatch映射到组件props中

二、基本开发流程

  1. 创建store文件夹
  2. 创建index.js作为入口文件,主要作用是通过import { configureStore } from "@reduxjs/toolkit"来创建并导出一个store
import { configureStore } from "@reduxjs/toolkit"
// 导入counter模块下的reducer函数
import counterReducer from "./features/counter"

const store = configureStore({
  reducer: {
   // 每个reducer都可以理解成一个子仓库
    counter: counterReducer
  }
})

export default store
  1. 下面完成counter模块的reducer的实现,就是实现一个计数器的功能,主要依赖@reduxjs/toolkit里的createSlice(),使用createSlice可以返回一个Slice对象,这个Slice对象里面有当前store模块的reducer和actions。怎么创建一个Slice呢?我们只需要传入一个配置对象
import { createSlice } from "@reduxjs/toolkit"

const counterSlice = createSlice({
  name: "counter", // store的名字
  initialState: { 
    counter: 100
  },
  reducers: {
    addNumber(state, action) {
       // 这里可以拿到state直接赋值,在最开始的写法中我们需要通过{ ...state, ...newObj }返回一个新的对象
      state.counter = state.counter + action.payload
    },
    subNumber(state, { payload }) {
      state.counter = state.counter - payload
    }
  }
})
// 创建好store之后,我们仍需要将Slice对象的actions和reducer导出
// 这里的addNumber和subNumber其实就是一个actionCreator(),调用即可获得一个action对象,
// 例如 addNumber(12)的返回值是{ type: 'addNumber', payload: 12}这样一个action对象
export const { addNumber, subNumber } = counterSlice.actions
export default counterSlice.reducer

需要注意的是,在@reduxjs/toolkit中我们可以直接对state进行赋值,因为已经帮我们封装好了,官网有解释到:

// Redux Toolkit 允许在 reducers 中编写 "mutating" 逻辑。  
// 它实际上并没有改变 state,因为使用的是 Immer 库,检测到“草稿 state”的变化并产生一个全新的  
// 基于这些更改的不可变的 state。
  1. 跟Vuex和Pinia一样,我们需要再main.js(index.js)中引入导出的store,并且通过Provider注册store
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import App from './App'
import store from './store'

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
  // <React.StrictMode>
  <Provider store={store}>
    <App />
  </Provider>
  // </React.StrictMode>
)
  1. 现在我们就可以在Home和About两个子组件中使用counterStore中的数据,在About组件中我们只使用store中的数据不修改,在Home组件中我们既读取也修改。
// About.jsx
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'

export class About extends PureComponent {
  render() {
    const { counter } = this.props

    return (
      <div>
        <h2>About Counter: {counter}</h2>
      </div>
    )
  }
}

const mapStateToProps = (state) => ({
  // state可以看成store,里面有counter这个模块,counter里面有counter这个数据
  counter: state.counter.counter
})

export default connect(mapStateToProps)(About)
// Home.jsx
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { addNumber } from '../store/features/counter'

export class Home extends PureComponent {
  render() {
    const { counter, increament } = this.props

    return (
      <div>
        <h2>Home Counter: {counter}</h2>
        <button onClick={(e) => increament(5)}>+5</button>
        <button onClick={(e) => increament(8)}>+8</button>
        <button onClick={(e) => increament(18)}>+18</button>
      </div>
    )
  }
}

const mapStateToProps = (state) => ({
  counter: state.counter.counter
})

const mapDispatchToProps = (dispatch) => ({
  increament(num) {
    dispatch(addNumber(num))
  }
})

export default connect(mapStateToProps, mapDispatchToProps)(Home)

这样一个简单的计数器功能就实现了

image.png

三、异步操作

下面,我们对上面的案例进行升级,使他更像开发中的情况(开发中我们肯定会有多个Slice),我们新建一个模块叫做home,home中存有轮播图数组和recommends数组,并在home中发送异步请求

import homeReducer from './features/home'

const store = configureStore({
  reducer: {
    counter: counterReducer,
    home: homeReducer // index.js中别忘了引入home模块
  }
})
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'

const homeSlice = createSlice({
  name: 'home',
  initialState: {
    banners: [],
    recommends: []
  },
  reducers: {
    changeBanners(state, { payload }) {
      state.banners = payload
    },
    changeRecommends(state, { payload }) {
      state.recommends = payload
    }
  },
  // 当Slice中需要发送异步请求时,需要配置这个函数
  extraReducers: (builder) => {
    builder
      .addCase(homeMultidataAction.pending, (state, action) => {
        console.log('fetchHomeMultidataAction pending')
      })
      .addCase(homeMultidataAction.fulfilled, (state, { payload }) => {
        state.banners = payload.data.banner.list
        state.recommends = payload.data.recommend.list
      })
  }
})

export const { changeBanners, changeRecommends } = homeSlice.actions
// thunk函数允许执行异步逻辑, 通常用于发出异步请求。
// createAsyncThunk 创建一个异步action,方法触发的时候会有三种状态:
// pending(进行中)、fulfilled(成功)、rejected(失败)
export const homeMultidataAction = createAsyncThunk('fetch/homemultidata', async () => {
  // 1.发送网络请求, 获取数据
  const res = await axios.get('http://123.207.32.32:8000/home/multidata')
  // 2.返回结果, 那么action状态会变成fulfilled状态,这里返回的结果会变成action的payload
  return res.data
})
export default homeSlice.reducer

现在我们在Home组件中发起的componentDidMount函数中发起请求,并且获取仓库中的异步数据进行展示: Home.jsx

import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { addNumber } from '../store/features/counter'
import { homeMultidataAction } from '../store/features/home'

export class Home extends PureComponent {
  componentDidMount() {
    this.props.getHomeMultidata()
  }

  render() {
    const { counter, increament, banners, recommends } = this.props

    return (
      <div>
        <h2>Home Counter: {counter}</h2>
        <button onClick={(e) => increament(5)}>+5</button>
        <button onClick={(e) => increament(8)}>+8</button>
        <button onClick={(e) => increament(18)}>+18</button>
        展示数据
        <div className='banner'>
          <h2>轮播图展示</h2>
          <ul>
            {banners.map((item, index) => {
              return <li key={index}>{item.title}</li>
            })}
          </ul>
        </div>
        <div className='recommend'>
          <h2>推荐的展示</h2>
          <ul>
            {recommends.map((item, index) => {
              return <li key={index}>{item.title}</li>
            })}
          </ul>
        </div>
      </div>
    )
  }
}

const mapStateToProps = (state) => ({
  counter: state.counter.counter
})

const mapDispatchToProps = (dispatch) => ({
  increament(num) {
    dispatch(addNumber(num))
  },
  getHomeMultidata() {
    dispatch(homeMultidataAction())
  }
})

export default connect(mapStateToProps, mapDispatchToProps)(Home)