react项目中使用redux

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

1.1 基本使用

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

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

javascript 复制代码
import { createStore } from 'redux'
const store = createStore()

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

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

javascript 复制代码
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函数就行了。

javascript 复制代码
import counterReducer from './features/counter'
const store = createStore(counterReducer)

export default store

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

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

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

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

jsx 复制代码
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,具体的使用如下:

jsx 复制代码
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,我们就可以直接使用了,就不用再去反复使用生命周期钩子函数了。

jsx 复制代码
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使用。

javascript 复制代码
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时并不能向往常一样直接返回一个对象,而是要返回一个函数,然后我们可以在这个函数中使用我们所需要的异步请求。

javascript 复制代码
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之后,我们只需要正常使用就行了,不需要额外的操作。

jsx 复制代码
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串联在一起。

javascript 复制代码
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 在浏览器中开启调试

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

javascript 复制代码
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,具体的使用方式如下:

javascript 复制代码
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只需要在这个对象里配置就行了。

javascript 复制代码
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
less 复制代码
**createSlice**函数所传递的对象的各个键的含义,具体可参考官方文档[https://redux.js.org/introduction/why-rtk-is-redux-today](https://redux.js.org/introduction/why-rtk-is-redux-today)

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

jsx 复制代码
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的方式命名,第二个参数是一个回调函数,我们可以再这个回调函数中写我们的异步操作,具体如下:

javascript 复制代码
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;

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

jsx 复制代码
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,具体的使用如下:

jsx 复制代码
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。


相关推荐
EricXJ2 小时前
useMutation Hook 使用指南
前端·react.js·typescript
小浣熊喜欢揍臭臭6 小时前
手动搭建并配置react项目(webpack5)
前端·react.js·前端框架
阿豪啊6 小时前
React入门(三)-封装 Fetch 以及增加接口模拟请求
react.js
太阳花ˉ10 小时前
React(七):Redux
react.js
HaanLen1 天前
React19源码系列之Hooks(useRef)
javascript·react.js
前端大白话1 天前
React 中shouldComponentUpdate生命周期方法的作用,如何利用它优化组件性能?
react.js
凉生阿新1 天前
【React】基于 React+Tailwind 的 EmojiPicker 选择器组件
前端·react.js·前端框架
公子小六1 天前
ASP.NET Core WebApi+React UI开发入门详解
react.js·ui·c#·asp.net·.netcore
学渣y1 天前
React-响应事件
前端·javascript·react.js
晚风3081 天前
React
react.js