1. 项目中使用redux
1.1 只使用不修改
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
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 = {
counter: store.getState().counter
}
}
componentDidMount() {
store.subscribe(() => {
const state = store.getState()
this.setState({ counter: state.counter })
})
}
render() {
const { counter } = this.state
return (
<div>
<h2>App Counter: {counter}</h2>
<div className='pages'>
<Home/>
<Profile/>
</div>
</div>
)
}
}
export default App
import React, { PureComponent } from 'react'
import store from "../store"
import { addNumberAction } from '../store/actionCreators'
export class Home extends PureComponent {
constructor() {
super()
this.state = {
counter: store.getState().counter
}
}
componentDidMount() {
store.subscribe(() => {
const state = store.getState()
this.setState({ counter: state.counter })
})
}
addNumber(num) {
store.dispatch(addNumberAction(num))
}
render() {
const { counter } = this.state
return (
<div>
<h2>Home Counter: {counter}</h2>
<div>
<button onClick={e => this.addNumber(1)}>+1</button>
<button onClick={e => this.addNumber(5)}>+5</button>
<button onClick={e => this.addNumber(8)}>+8</button>
</div>
</div>
)
}
}
export default Home
import React, { PureComponent } from 'react'
import store from "../store"
import { subNumberAction } from '../store/actionCreators'
export class Profile extends PureComponent {
constructor() {
super()
this.state = {
counter: store.getState().counter
}
}
componentDidMount() {
store.subscribe(() => {
const state = store.getState()
this.setState({ counter: state.counter })
})
}
subNumber(num) {
store.dispatch(subNumberAction(num))
}
render() {
const { counter } = this.state
return (
<div>
<h2>Profile Counter: {counter}</h2>
<div>
<button onClick={e => this.subNumber(1)}>-1</button>
<button onClick={e => this.subNumber(5)}>-5</button>
<button onClick={e => this.subNumber(8)}>-8</button>
<button onClick={e => this.subNumber(20)}>-20</button>
<button onClick={e => this.subNumber(100)}>-100</button>
</div>
</div>
)
}
}
export default Profile
import { createStore } from "redux"
import reducer from "./reducer"
const store = createStore(reducer)
export default store
import * as actionTypes from "./constants"
const initialState = {
counter: 100
}
function reducer(state = initialState, action) {
switch (action.type) {
case actionTypes.ADD_NUMBER:
return { ...state, counter: state.counter + action.num }
case actionTypes.SUB_NUMBER:
return { ...state, counter: state.counter - action.num }
default:
return state
}
}
export default reducer
export const ADD_NUMBER = "add_number"
export const SUB_NUMBER = "sub_number"
import * as actionTypes from "./constants"
export const addNumberAction = (num) => ({
type: actionTypes.ADD_NUMBER,
num
})
export const subNumberAction = (num) => ({
type: actionTypes.SUB_NUMBER,
num
})
2. 使用 react-redux
npm install react-redux
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>
)
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
export class about extends PureComponent {
render() {
const { counter } = this.props
return <div>About Page: {counter}</div>
}
}
const mapStateToProps = state => ({
counter: state.counter
})
export default connect(mapStateToProps)(about)
1.2 在组件中修改值
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { addNumberAction, subNumberAction } from '../store/actionCreators'
export class about extends PureComponent {
calcNumber(num, isAdd) {
if (isAdd) {
this.props.addNumber(num)
} else {
this.props.subNumber(num)
}
}
render() {
const { counter } = this.props
return (
<div>
<h2>About Page: {counter}</h2>
<button onClick={e => this.calcNumber(6, true)}>+6</button>
<button onClick={e => this.calcNumber(88, true)}>+88</button>
<button onClick={e => this.calcNumber(6, false)}>-6</button>
<button onClick={e => this.calcNumber(88, false)}>-88</button>
</div>
)
}
}
const mapStateToProps = state => ({
counter: state.counter
})
const mapDispatchToProps = dispatch => ({
addNumber: num => dispatch(addNumberAction(num)),
subNumber: num => dispatch(subNumberAction(num))
})
export default connect(mapStateToProps, mapDispatchToProps)(about)
1.3 请求服务器中的数据并且修改state中的值
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import axios from 'axios'
import { changeBannersAction, changeRecommendsAction } from '../store/actionCreators'
export class Category extends PureComponent {
componentDidMount() {
axios.get('XXXX').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 => ({
changeBanners: banners => dispatch(changeBannersAction(banners)),
changeRecommends: recommends => dispatch(changeRecommendsAction(recommends))
})
export default connect(null, mapDispatchToProps)(Category)
export const CHANGE_BANNERS = 'change_banners'
export const CHANGE_RECOMMENDS = 'change_recommends'
import store from "."
import * as actionTypes from "./constants"
export const addNumberAction = num => ({ type: actionTypes.ADD_NUMBER, num })
export const subNumberAction = num => ({ type: actionTypes.SUB_NUMBER, num })
export const changeBannersAction = banners =>({
type: actionTypes.CHANGE_BANNERS,
banners
})
export const changeRecommendsAction = recommends =>({
type: actionTypes.CHANGE_RECOMMENDS,
recommends
})
import * as actionTypes from './constants'
const initialState = {
counter: 100,
banners: [],
recommends: []
}
function reducer(state = initialState, action) {
switch (action.type) {
case actionTypes.ADD_NUMBER:
return { ...state, counter: state.counter + action.num }
case actionTypes.SUB_NUMBER:
return { ...state, counter: state.counter - action.num }
case actionTypes.CHANGE_BANNERS:
return { ...state, banners: state.banners }
case actionTypes.CHANGE_RECOMMENDS:
return { ...state, recommends: state.recommends }
default:
return state
}
}
export default reducer
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { addNumberAction, subNumberAction } from '../store/actionCreators'
export class about extends PureComponent {
calcNumber(num, isAdd) {
if (isAdd) {
this.props.addNumber(num)
} else {
this.props.subNumber(num)
}
}
render() {
const { counter, banners, recommends } = this.props
return (
<div>
<h2>About Page: {counter}</h2>
<button onClick={e => this.calcNumber(6, true)}>+6</button>
<button onClick={e => this.calcNumber(88, true)}>+88</button>
<button onClick={e => this.calcNumber(6, false)}>-6</button>
<button onClick={e => this.calcNumber(88, false)}>-88</button>
<div>
<ul>
{banners.map((item, index) => (
<li key={index}>{item.title}</li>
))}
</ul>
<h2>推荐列表:</h2>
<ul>
{recommends.map((item, index) => (
<li key={index}>{item.title}</li>
))}
</ul>
</div>
</div>
)
}
}
const mapStateToProps = state => ({
counter: state.counter,
banners: state.banners,
recommends: state.recommends
})
const mapDispatchToProps = dispatch => ({
addNumber: num => dispatch(addNumberAction(num)),
subNumber: num => dispatch(subNumberAction(num))
})
export default connect(mapStateToProps, mapDispatchToProps)(about)
1.4 优化
- 上述代码缺陷:
- 必须将网络请求的异步代码放到组件的生命周期中来完成;
- 事实上,网络请求到的数据也属于我们状态管理的一部分,更好的一种方式应该是将其交给 redux 来管理
import { createStore, applyMiddleware } from "redux"
import thunk from "redux-thunk"
import reducer from "./reducer"
const store = createStore(reducer, applyMiddleware(thunk))
export default store
...
export const fetchHomeMultidataAction = () => {
function foo(dispatch, getState) {
console.log("foo function execution------", getState().counter)
axios.get("xxx").then(res => {
const banners = res.data.data.banner.list
const recommends = res.data.data.recommend.list
dispatch(changeBannersAction(banners))
dispatch(changeRecommendsAction(recommends))
})
}
return foo
}
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: {this.props.counter}</h2>
</div>
)
}
}
const mapStateToProps = state => ({
counter: state.counter
})
const mapDispatchToProps = dispatch => ({
fetchHomeMultidata: () => dispatch(fetchHomeMultidataAction())
})
export default connect(mapStateToProps, mapDispatchToProps)(Category)
1.5 分离 store
- 当组件过多时,如果将所有的store都放到一个文件中,此时会造成文件臃肿,这种情况下要将这些文件拆分成单独的文件,方便管理与修改。
import * as actionTypes from "./constants"
import axios from "axios"
export const changeBannersAction = banners => ({
type: actionTypes.CHANGE_BANNERS,
banners
})
export const changeRecommendsAction = recommends => ({ type: actionTypes.CHANGE_RECOMMENDS, recommends })
export const fetchHomeMultidataAction = () => (dispatch, getState) => {
axios.get("xxx").then(res => {
const banners = res.data.data.banner.list
const recommends = res.data.data.recommend.list
dispatch(changeBannersAction(banners))
dispatch(changeRecommendsAction(recommends))
})
}
export const CHANGE_BANNERS = "change_banners"
export const CHANGE_RECOMMENDS = "change_recommends"
import * as actionTypes from "./constants"
const initialState = {
banners: [],
recommends: []
}
function reducer(state = initialState, action) {
switch (action.type) {
case actionTypes.CHANGE_BANNERS:
return { ...state, banners: action.banners }
case actionTypes.CHANGE_RECOMMENDS:
return { ...state, recommends: action.recommends }
default:
return state
}
}
export default reducer
import reducer from "./reducer"
export default reducer
export * from "./actionCreators"
......
import { createStore, applyMiddleware, compose, combineReducers } from "redux"
import thunk from "redux-thunk"
import counterReducer from "./counter"
import homeReducer from "./home"
import userReducer from "./user"
const reducer = combineReducers({
counter: counterReducer,
home: homeReducer,
user: userReducer
})
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)))
export default store
2. 使用 redux-toolkit
2.1 store 中的子文件
import { createSlice } from "@reduxjs/toolkit"
const counterSlice = createSlice({
name: "counter",
initialState: {
counter: 888
},
reducers: {
addNumberAction: (state, { payload }) => {
state.counter += payload
},
subNumberAction: (state, { payload }) => {
state.counter -= payload
}
}
})
export const { addNumberAction, subNumberAction } = counterSlice.actions
export default counterSlice.reducer
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"
import axios from "axios"
export const fetchHomeMultidataAction = createAsyncThunk("fetch/homemultidata", async (extraInfo, { dispatch, getState }) => {
const res = await axios.get("xxx")
const banners = res.data.data.banner.list
const recommends = res.data.data.recommend.list
dispatch(changeBannersAction(banners))
dispatch(changeRecommendsAction(recommends))
return res.data
})
const homeSlice = createSlice({
name: "home",
initialState: {
banners: [],
recommends: []
},
reducers: {
changeBannersAction(state, { payload }) {
state.banners = payload
},
changeRecommendsAction(state, { payload }) {
state.recommends = payload
}
},
extraReducers: builder => {
builder
.addCase(fetchHomeMultidataAction.pending, (state, action) => {
console.log("fetchHomeMultidataAction pending")
})
.addCase(fetchHomeMultidataAction.fulfilled, (state, { payload }) => {
state.banners = payload.data.banner.list
state.recommends = payload.data.recommend.list
})
}
})
export const { changeBannersAction, changeRecommendsAction } = homeSlice.actions
export default homeSlice.reducer
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
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(
<Provider store={store}>
<App />
</Provider>
)
import React, { PureComponent } from "react"
import { connect } from "react-redux"
import { addNumberAction } from "../store/features/counter"
import { fetchHomeMultidataAction } from "../store/features/home"
export class Home extends PureComponent {
componentDidMount() {
this.props.fetchHomeMultidata()
}
addNumber(num) {
this.props.addNumber(num)
}
render() {
const { counter } = this.props
return (
<div>
<h2>Home Counter: {counter}</h2>
<button onClick={e => this.addNumber(5)}>+5</button>
<button onClick={e => this.addNumber(8)}>+8</button>
</div>
)
}
}
const mapStateToProps = state => ({
counter: state.counter.counter
})
const mapDispatchToProps = dispatch => ({
addNumber: num => dispatch(addNumberAction(num)),
fetchHomeMultidata: () => dispatch(fetchHomeMultidataAction({ name: "kobe", age: 18 }))
})
export default connect(mapStateToProps, mapDispatchToProps)(Home)
import React, { PureComponent } from "react"
import { connect } from "react-redux"
import { subNumberAction } from "../store/features/counter"
export class Profile extends PureComponent {
subNumber(num) {
this.props.subNumber(num)
}
render() {
const { counter, banners, recommends } = this.props
return (
<div>
<h2>Profile: {counter}</h2>
<button onClick={e => this.subNumber(5)}>-5</button>
<button onClick={e => this.subNumber(8)}>-8</button>
<div className="banners">
<h2>轮播图列表:</h2>
<ul>
{banners.map((item, index) => (
<li key={index}>{item.title}</li>
))}
</ul>
</div>
<div className="recommends">
<h2>展示的列表:</h2>
<ul>
{recommends.map((item, index) => (
<li key={index}>{item.title}</li>
))}
</ul>
</div>
</div>
)
}
}
const mapStateToProps = state => ({
counter: state.counter.counter,
banners: state.home.banners,
recommends: state.home.recommends
})
const mapDispatchToProps = dispatch => ({
subNumber: num => dispatch(subNumberAction(num))
})
export default connect(mapStateToProps, mapDispatchToProps)(Profile)
3. redux 封装
3.1 封装日志信息
function log(store) {
const next = store.dispatch
function logAndDispatch(action) {
console.log("当前派发的action: ", action)
next(action)
console.log("派发之后的结果: ", store.getState())
}
store.dispatch = logAndDispatch
}
export default log
3.2 封装thunk
function thunk(store) {
const next = store.dispatch
function dispatchThunk(action) {
if (typeof action === "function") {
action(store.dispatch, store.getState)
} else {
next(action)
}
}
store.dispatch = dispatchThunk
}
export default thunk
3.3 合并中间件
function applyMiddleware(store, ...fns) {
fns.forEach(fn => fn(store))
}
export default applyMiddleware
import log from "./log"
import thunk from "./thunk"
import applyMiddleware from "./applyMiddleware"
export { log, thunk, applyMiddleware }
3.4 使用
import { applyMiddleware } from "./middleware"
...
applyMiddleware(store, log, thunk)