一、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 useSelector 和useDispatch ,前者用来获取我们保存的数据,后者用来当我们需要修改数据时派发action,这两个hooks只是为了我们在页面使用redux时不用去使用connect 这个高阶函数以及编写相对应的映射函数mapStateToProps 、mapDispatchToProps,具体的使用如下:
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。