在react项目中使用redux

本文详细介绍了在React项目中使用React-Redux和@redux/toolkit库进行状态管理的方法,包括类组件中的基本操作、异步处理、多个reducer整合、以及使用hooks进行简化。重点讲解了如何创建store、reducer、action以及如何在组件间传递state和action,同时提到了性能优化的浅层比较技巧。
摘要由CSDN通过智能技术生成

一、react-redux的使用(类组件)

1.1 基本使用

在react项目中使用redux需要依赖于react-redux这个库,然后他的基本使用与我们redux的普通使用类似,都要创建store、reducer以及action。

首先,我们需要先创建一个store,并且在项目的入口文件中引入该store。

import { createStore } from 'redux'
const store = createStore()

export default store
import { Provider } from "react-redux";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

根据以上代码,我们就可以在项目中使用redux了,在store.js文件中,我们需要编写项目中我们需要使用的reducer传递给createStore函数。

import { ADD_NUMBER, DECREASE_NUMBER } from './constants'
const initialState = {
    num: 200
}

function reducer(state = initialState, payload) {
    const { type, num } = payload
    switch (type) {
        case ADD_NUMBER: {
          return { ...state, num: state.num + num };
        }
        case DECREASE_NUMBER: {
          return { ...state, num: state.num - num };
        }
        default:
          return state;
      }
}
export default reducer

为了项目以后好维护,我采取了常量的方式来代表我们发起的action type,编写号reducer之后,我们直接传递给createStore函数就行了。

import counterReducer from './features/counter'
const store = createStore(counterReducer)

export default store

编写好以上代码,我们如果想要在页面中使用redux,我们还需要定义action来驱动redux。

import {
  ADD_NUMBER,
  DECREASE_NUMBER,
} from "./constants";
export const addNumberAction = (num) => ({
  type: ADD_NUMBER,
  num,
});

export const decreaseNumberAction = (num) => ({
  type: DECREASE_NUMBER,
  num,
});

做好以上工作,我们终于可以在页面中正式使用redux了。

import React, { PureComponent } from 'react'
import store from '../store'
import { addNumberAction } from '../store/actionCreators'

export class Home extends PureComponent {
  constructor() {
    super()
    this.state = {
      num: store.getState().num
    }
  }
  componentDidMount() {
    store.subscribe(() => {
      const { num } = store.getState()
      this.setState({ num })
    })
  }
  addNumber = (number) => {
    store.dispatch(addNumberAction(number))
  }
  render() {
    return (
      <div>
        <h2>Home Counter: { this.state.num }</h2>
        <div>
          <button onClick={() => this.addNumber(1)}>+1</button>
          <button onClick={() => this.addNumber(5)}>+5</button>
        </div>
      </div>
    )
  }
}

export default Home

但是由于我们可能不止在这个页面使用这个操作,因此我们可能需要不断重复的使用类组件的生命周期钩子函数,考虑到这一点,react-redux这个库提供了connect这个函数,他接收两个参数,返回值是一个高阶组件,这两个参数分别为redux中的state以及action,具体的使用如下:

const mapStateToProps = (state) => ({
  num: state.counter.num,
});
const mapDispatchToProps = (dispatch) => ({
  addNumber(number) {
    dispatch(addNumberAction(number));
  },
  decreaseNumber(number) {
    dispatch(decreaseNumberAction(number));
  },
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);

编写了以上代码中,redux中的state以及action就传递给了props,我们就可以直接使用了,就不用再去反复使用生命周期钩子函数了。

export class About extends PureComponent {
  calcNumber = (number, isAdd) => {
    if (isAdd) {
      this.props.addNumber(number);
    } else {
      this.props.decreaseNumber(number);
    }
  };
  render() {
    const { num } = this.props;
    return (
      <div>
        <h2>About Counter: {num}</h2>
        <div>
          <button onClick={() => this.calcNumber(6, true)}>+6</button>
          <button onClick={() => this.calcNumber(6, false)}>-6</button>
        </div>
      </div>
    );
  }
}

1.2 redux的异步使用

        我们在写项目时有时候需要向服务器请求数据,并且这些数据是多个页面共享的,因此我们需要将这些数据保存在redux的state中,而为了保持一致性,我们需要发起action时在action里发送网络请求,而不是在页面中发送网络请求获取数据之后再发起action,将数据保存在state中,这个时候我们需要用到react-redux这个库提供给我们的中间件thunk函数,并且在创建store时配合redux中的applyMiddleware使用。

import homeReducer from './features/home'
import { createStore, applyMiddleware } from 'redux'
import {thunk} from 'redux-thunk'

const store = createStore(homeReducer, applyMiddleware(thunk))

export default store

        在创建store时使用之后,我们创建action时并不能向往常一样直接返回一个对象,而是要返回一个函数,然后我们可以

export const fetchHomeMultidataAction = () => {
  return function (dispatch) {
    // 异步操作: 网络请求
    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: SET_BANNERS, banners })
      //   dispatch({ type: SET_RECOMMENDS, recommends })
      dispatch(setBannerAction(banners));
      dispatch(setRecommendsAction(recommends));
    });
  };
};

编写好action之后,我们只需要正常使用就行了,不需要额外的操作。

import React, { PureComponent } from "react";
import { connect } from "react-redux";
import { fetchHomeMultidataAction } from "../store/features/home";

export class Category extends PureComponent {
  componentDidMount() {
    this.props.fetchHomeMultidata()
  }
  render() {
    return (
      <div className="category">
        <h2>category</h2>
      </div>
    );
  }
}
const mapDispatchToProps = dispatch => ({
    fetchHomeMultidata() {
        dispatch(fetchHomeMultidataAction())
    }
})
export default connect(null, mapDispatchToProps)(Category);

1.3 多个reducer

        有时候我们的项目可能不止一个reducer,因此我们需要使用redux为我们提供的combineReducers函数来将多个reducer串联在一起。

import counterReducer from './features/counter'
import homeReducer from './features/home'
const reducer = combineReducers({
  counter: counterReducer,
  home: homeReducer
})
const store = createStore(reducer, applyMiddleware(thunk))

export default store

        这样子我们的项目就能使用多个reducer了,然后我们的使用变成了state.xxx.xxx,state后面分别为你使用combineReducers函数传入的对象的键以及定义的state。

1.4 在浏览器中开启调试

        为了方便调试,我们会在浏览器安装相关的插件(react-devtools)来方便我们的开发,但是当我们运行项目时却没有出现调试面板,这是因为这个插件不像vue一样都给你集成好,而是需要我们去手动开启。因此我们在创建store时需要去手动开启调试。

import { createStore, applyMiddleware, compose, combineReducers } from 'redux'
import {thunk} from 'redux-thunk'
import counterReducer from './features/counter'
import homeReducer from './features/home'
const composeEnhancers = (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
const reducer = combineReducers({
    counter: counterReducer,
    home: homeReducer
})
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)))

export default store

以上代码就是我们创建store时的最终模式。

二、@redux/toolkit库的使用(类组件)

        尽管react-redux已经足够方便,但是当项目中的模块一多起来,为了规范,我们往往需要创建大量的文件来写这个redux。如图所示:

因此redux为了简化这些操作,社区提供了@redux/toolkit这个库来简化操作。

1.1 @redux/toolkit库的基本使用

        @redux/toolkit这个库废弃了原来创建store的函数createStore,改成了configureStore,具体的使用方式如下:

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './features/counter'
import homeReducer from './features/home'
const store = configureStore({
    reducer: {
        counter: counterReducer,
        home: homeReducer
    }
})

export default store

        相较于之前的创建方式,大大简洁了。而过去我们使用redux时为了规范需要创建不同的文件,而@redux/toolkit这个库提供了createSlice这个函数帮助我们节省这些操作,这个函数需要传递一个对象给他,过去我们所需要的action、reducer只需要在这个对象里配置就行了。

import { createSlice } from '@reduxjs/toolkit'

const counterSlice = createSlice({
    name: 'counter',
    initialState: {
        counter: 100
    },
    reducers: {
        addNumber(state, action) {
            state.counter = state.counter + action.payload
        },
        subNumber(state, action) {
            state.counter = state.counter - action.payload
        }
    }
})
export const {addNumber, subNumber} = counterSlice.actions
export default counterSlice.reducer

createSlice函数所传递的对象的各个键的含义,具体可参考官方文档https://redux.js.org/introduction/why-rtk-is-redux-today

我们写好配置文件并导出reducer以及actions之后,我们就可以结合react-redux在页面中使用redux了。

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

import { fetchHomeDataAction } from '../store/features/home'

export class Home extends PureComponent {
  componentDidMount() {
    this.props.fetchHomeData()
  }
  addNumber = (num) => {
    this.props.addNumber(num)
  }
  render() {
    const { counter } = this.props
    return (
      <div>
        <h2>Home Counter: {counter}</h2>
        <button onClick={() => this.addNumber(5)}>+5</button>
        <button onClick={() => this.addNumber(8)}>+8</button>
      </div>
    )
  }
}
const mapStateToProps = (state) => ({
  counter: state.counter.counter
})
const mapDispatchToProps = (dispatch) => ({
  addNumber(num) {
    dispatch(addNumber(num))
  },
  fetchHomeData() {
    dispatch(fetchHomeDataAction())
  }
})
export default connect(mapStateToProps, mapDispatchToProps)(Home)

        由以上代码我们可以知道@redux/toolkit这个库只是简化了我们创建store时的复杂操作,但在使用时还是跟往常一样。

1.2 @redux/toolkit配置异步

        @redux/toolkit这个库提供了createAsyncThunk这个函数,这个函数接收两个参数,第一个是该thunk的type,我们可以随意取名,不过为了规范,一般会以xxx/xxx的方式命名,第二个参数是一个回调函数,我们可以再这个回调函数中写我们的异步操作,具体如下:

import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";

export const fetchHomeDataAction = createAsyncThunk(
  "fetch/homeData",
  async (extraInfo, { dispatch }) => {
    const { data } = await axios.get(
      "http://123.207.32.32:8000/home/multidata"
    );
    return data.data;
  }
);

const homeSlice = createSlice({
  name: "home",
  initialState: {
    banners: [],
    recommends: [],
  },
  reducers: {
    setBanners(state, { payload }) {
      state.banners = payload;
    },
    setRecommends(state, { payload }) {
      state.recommends = payload;
    },
  },
  extraReducers: (builder) => {
    builder.addCase(fetchHomeDataAction.fulfilled, (state, { payload }) => {
      state.banners = payload.banner.list;
      state.recommends = payload.recommend.list;
    });
  },
  // 这种写法已经被官方移除
  //   extraReducers: {
  //     [fetchHomeDataAction.fulfilled](state, { payload }) {
  //       state.banners = payload.banner.list;
  //       state.recommends = payload.recommend.list;
  //     },
  //   },
});

export const { setBanners, setRecommends } = homeSlice.actions;
export default homeSlice.reducer;

        编写好这个文件之后,使用方式跟往常还是一模一样的。

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

import { fetchHomeDataAction } from '../store/features/home'

export class Home extends PureComponent {
  componentDidMount() {
    this.props.fetchHomeData()
  }
  addNumber = (num) => {
    this.props.addNumber(num)
  }
  render() {
    const { counter } = this.props
    return (
      <div>
        <h2>Home Counter: {counter}</h2>
        <button onClick={() => this.addNumber(5)}>+5</button>
        <button onClick={() => this.addNumber(8)}>+8</button>
      </div>
    )
  }
}
const mapStateToProps = (state) => ({
  counter: state.counter.counter
})
const mapDispatchToProps = (dispatch) => ({
  addNumber(num) {
    dispatch(addNumber(num))
  },
  fetchHomeData() {
    dispatch(fetchHomeDataAction())
  }
})
export default connect(mapStateToProps, mapDispatchToProps)(Home)

三、hook方式使用redux

        react-redux这个库提供两个hook useSelectoruseDispatch,前者用来获取我们保存的数据,后者用来当我们需要修改数据时派发action,这两个hooks只是为了我们在页面使用redux时不用去使用connect这个高阶函数以及编写相对应的映射函数mapStateToPropsmapDispatchToProps,具体的使用如下:

import React, {memo} from 'react'
import { useSelector, useDispatch } from 'react-redux'

const App = memo((props) => {
    const {counter} = useSelector(state => ({
        counter: state.counter.counter
    }))
    const dispatch = useDispatch()
    const addOrSubNumberHandler = (num, isAdd = true) => {
        if (isAdd) {
            dispatch(addNum(num))
        } else {
            dispatch(subNum(num))
        }
    }
    return (
        <div>
            <p>当前计数: {counter}</p>
            <button onClick={ () => addOrSubNumberHandler(1) }>+1</button>
            <button onClick={ () => addOrSubNumberHandler(1, false) }>-1</button>
            <Home />
        </div>
    )
})

        当我们使用useSelector这个hook的时候,我们需要注意他监听的是整个state,因此当我们的state发生改变时他都会引起页面的重新渲染,这就对性能造成了极大的浪费,因为有些时候我们在一个组件里引入另外一个组件时,如果你子组件的应用的state没有发生变化,父组件中应用的state发生了变化,而这时候你使用useSelector这个hook他监听的是整个state,因此他不管你子组件应用的state有没有发生变化,他都给你重新渲染,因此这就造成了性能的浪费,所以当我们使用useSelector这个hook时,我们需要给他传递第二个参数,第二个参数是一个回调函数,该函数的作用是让其进行浅层比较(就是检测state中的哪个数据发生变化了,只有发生变化了才重新渲染),这个函数react-redux给我们提供了,就是shallowEqual。因此我们只需要将他引入使用就好了。

  • 29
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aklry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值