一、纯函数
1、react 中的纯函数
- react 中组件就被要求像是一个纯函数(因为还有类组件)
- redux 中有一个reducer 的概念,也是要求必须是一个纯函数
2、 纯函数的条件
- 确定的输入一定会产生确定的输出
- 函数在执行过程中,不能产生副作用
3、副作用的概念
表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响。比如修改了全局变量,修改参数或者改变了外部的存储
二、Redux 的三大原则
- 单一数据源:整个应用程序的state 被存储在一颗object tree 中,并且这个object tree 只存储在一个store 中
- state 是只读的:唯一修改state 的方法一定是触发action
- 使用纯函数来执行修改:通过reducer 将旧的state 和actions 联系在一起,并且返回一个新的state
三、Redux 的核心概念
1、Store
- 定义初始化数据,通过reducer 存放到Store 中,所有Store 中的数据都是来源于reducer 函数
2、action
- Redux 中所有数据的变化,必须通过派发(dispatch)action 来更新
- action 是一个普通的JavaScript 对象,用来描述这次更新的type 和content
- 强制使用action 的好处是可以清晰的知道数据到底发生了什么样的变化,所有的数据变化都是可跟踪、可预测的
3、reducer
- reducer 是一个纯函数
- reducer 做的事情是将传入的state 和action 结合起来生成一个新的state
示例:
// store/index.js
import { createStore } from "redux"
import reducer from "./reducer"
const store = createStore(reducer)
export default store
// store/reducer.js
import * as actionTypes from "./contants"
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
// store/actionCreators.js
import * as actionTypes from "./contants"
export const addNumberAction = (num) => ({
type: actionTypes.ADD_NUMBER,
num
})
export const subNumberAction = (num) => ({
type: actionTypes.SUB_NUMBER,
num
})
// store/constants.js
export const ADD_NUMBER = "add_number"
export const SUB_NUMBER = "sub_number"
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>{ counter }</h2>
<button onClick={ e=> this.addNumber(1)}>+1</button>
</div>
)
}
}
四、react-redux
- npm install react-redux
- react-redux 是一个高阶组件
- connect() 是高阶函数,接收两个函数作为参数,返回值是一个高阶组件,接收组件作为参数
- connect 的第一个参数:将store 中的state 中的数据,映射到props 中
- connect 的第二个参数:将store 中的dispatch 中的action,映射到props 中
- 当组件所依赖的store 中的数据发生变化时,组件会重新调用render 函数
npm install react-redux
// index.js
import { Provider } from 'react-redux'
import store from './store'
root.render(
<React.StriceMode>
<Provider store={store}>
<App/>
</Provider>
</React.StriceMode>
)
// home.jsx
import { connect } from "react-redux"
export class Home extends PureComponent {
calcNumber(num, isAdd) {
if(isAdd) {
this.props.addNumber(num)
} else {
this.props.subNumber(num)
}
}
render() {
const { counter } = this.props
return (
<div>
<h2>{ counter }</h2>
</div>
)
}
}
// 当组件所依赖的store 中的数据发生变化时,组件会重新调用render 函数
// connect 的第一个参数:将store 中的state 中的数据,映射到props 中
const mapStateToProps = (state) => ({ counter: state.counter })
// connect 的第二个参数:将store 中的dispatch 中的action,映射到props 中
const mapDispatchToProps = (dispatch) => ({
addNumber: (num) => dispatch(addNumberAction(num));
subNumber: (num) => dispatch(subNumberAction(num));
})
// connect 本身是一个高阶函数,接收两个函数作为参数
// connect() 返回值是一个新函数,这个新函数是一个高阶组件,接收组件作为参数
export default connect( mapStateToProps, mapDispatchToProps )(Home)
五、redux-thunk 中间件 (处理redux 中的异步操作)
- 由于在store.dispatch(object) 只能派发对象,所以在redux 中做网络请求的异步操作时,是拿不到请求数据的,所以希望在store.dispatch(function) 派发时可以派发函数(本质派发action 还是要通过dispatch(object))
- npm install redux-thunk
// store/index.js
import { createStore, applyMiddleware } from "redux"
import thunk from "redux-thunk"
import reducer from "./reducer"
const store = createStore(reducer, applyMiddleware(thunk))
export default store
// actionCreators.js
export const changeBannersAction = (banners) => {
type: actionTypes.CHANGE_BANNERS,
banners
}
export const fetchHomeMultidataAction = () => {
return function(dispatch, getState) {
axios.get("http://").then(res => {
const banners = res.data.data.banner.list
// dispatch({ type: actionTypes.CHANGE_BANNERS, banners })
dispatch(changeBannersAction(banners))
})
}
}
// banners.jsx
import { connect } from "react-redux"
import { fetchHomeMultidataAction } from "../store/actionCreators.js"
export class Banners extends PureComponent {
componentDidMount() {
this.props.fetchHomeMultidata()
}
render() {
return (
<div>
<h2>{ this.props.counter }</h2>
</div>
)
}
}
const mapStateToProps = (state) => {
counter: state.counter
}
const mapDispatchToProps = (dispatch) => {
fetchHomeMultidata() {
dispatch(fetchHomeMultidataAction())
}
}
export default connect( mapStateToProps, mapDispatchToProps )(Banners)
六、combineReducers (redux 中的模块化)
- combineReducers 函数可以对多个reducer 进行合并
- combineReducers 的原理:
- combineReducers 函数是将传入的reducers 合并到一个对象中,最终返回一个combination的函数
- 在执行combination 函数的过程中,它会通过判断前后返回的数据是否相同来决定返回之前的state 还是新的state
- 新的state 会触发订阅者发生对应的刷新,而旧的state 就可以有效的自组织订阅者发生刷新
// combineReducers 的实现原理 function reducer( state = {}, action ) { return { counter: counterReducer(state.counter, action), home: homeReducer(state.home, action) } }
// store/index.js
import { combineReducers } from "redux"
import counterReducer from './counter/index.js'
import homeReducerfrom './home'
// 将多个reducer 合并在一起
const reducer = combineReducers({
counter: counterReducer,
home: homeReducer
})
const store = createStore(reducer, applyMiddeware(thunk))
export default store
// home.jsx
constructor() {
super()
counter: store.getState().counter.counter
}
七、redux-toolkit
- 安装redux-toolkit:npm install @reduxjs/toolkit npm install react-redux
- redux-toolkit 的核心API
- 1、configureStore: 包装createStore 以提供简化的配置选项和良好的默认值,它可以自动组合slice reducer,添加提供的任何Redux 中间件,redux-thunk 默认包含,并启用Redux DevTools Extension
- 2、createSlice:接受reducer 函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions
- 3、createAsyncThunk:接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected 基于该承诺分派动作类型的thunk
1、redux-toolkit 的基本使用
// store/index.js
impoort { configureStore } from '@reduxjs/toolkit'
import counterReducer from './features/counter'
const store = configureStore({
reducer: {
counter: counterReducer
}
})
export default store
// store/features/counter.js
impoort { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: "counter",
initialState: {
counter: 100
}
reducers: {
addNumber(state, action) {
const paylaod = action.payload
state.counter = state.counter + payload
},
subNumber(state, { payload }) {
const paylaod = action.payload
state.counter = state.counter - payload
}
}
})
export const { addNumber, subNumber } = counterSlice.actions
export default counterSlice.reducer
// src/index.js
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>
)
// src/App.jsx
import { connect } from "react-redux"
import { subNumber } from '../store/features/counter'
export class App extends PureComponent {
addNumber(num) {
this.props.addNumber(num)
}
subNumber(num) {
this.props.subNumber(num)
}
render() {
const { counter } = this.props
return (
<div>
<h2>counter: { counter }</h2>
<button onClick={ e=>this.addNumber(5) }>+5</button>
<button onClick={ e=>this.subNumber(5) }>-5</button>
</div>
)
}
}
const mapStateToProps = (state) => {
counter: state.counter.counter
}
const mapDispatchToProps = (dispatch) => {
addNumber(num) {
dispatch(addNumber(num))
}
subNumber(num) {
dispatch(subNumber(num))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
2、redux-toolkit 进行异步操作
// store/features/home.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
export const fetchHomeMultidataAction = createAsyncThunk("fetchhomedata", async () => {
const res = await axios.get("http")
return res.data
})
// fetchHomeMultidataAction 的另一种写法
// export const fetchHomeMultidataAction = createAsyncThunk("fetchhomedata", async (extraParams, { dispatch, getState }) => {
// const res = await axios.get("http")
// const banners = res.data.data.banner.list
// dispatch(changeBanners(banners))
// })
const homeSlice = createSlice({
name: "home",
initialState: {
banners: [],
},
reducers: {
changeBanners(state, { payload }) {
state.banners = payload
},
},
extraReducers: {
[fetchHomeMultidataAction.pending](state, action) {
console.log("异步操作的pending 状态")
},
[fetchHomeMultidataAction.fulfilled](state, { payload }) {
state.banners = payload.data.banner.list
},
[fetchHomeMultidataAction.rejected](state, action) {
console.log("异步操作的rejected 状态")
}
},
// extraReducers 的函数写法
// extraReducers: (builder) => {
// builder.addCase(fetchHomeMultidataAction.pending, (state, action) => {
// console.log("异步操作的pending 状态")
// }).addCase(fetchHomeMultidataAction.fulfilled, (state, { payload }) => {
// state.banners = payload.data.banner.list
// }).addCase(fetchHomeMultidataAction.rejected, (state, action) => {
// console.log("异步操作的rejected 状态")
// })
// }
})
export const { changeBanners } = homeSlice.actions
export default homeSlice.reducer
// src/Home.jsx
export class Home extends PureComponent {
componentDisMount() {
this.props.fetchHomeMultidata()
}
}
const mapDispatchToProps = (dispatch) => {
fetchHomeMultidata() {
dispatch(fetchHomeMultidataAction())
}
}
export default connect(mapDispatchToProps)(Home)
八、connect 的原理
import { PureComponent } from 'react'
import store from "../store"
export default function connect(mapStateToProps, mapDispatchToProps) {
return function(WrapperComponent) {
constructor(props) {
super(props)
this.state = mapStateToProps(store.getState())
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => {
this.setState(mapStateToProps(store.getState()))
})
}
componentWillUnmount() {
this.unsubscribe()
}
render() {
const stateObj = mapStateToProps(store.getState())
const dispatchObj = mapDispatchToProps(store.dispatch)
return <WrapperComponent {...this.props} {...stateObj} {...dispatchObj}/>
}
}
return NewComponent
}
九、中间件的原理
- redux-thunk 拦截dispatch 的原理:
- 当dispatch 进行派发时,redux-thunk 会对该派发做拦截,当dispatch 派发的是一个函数时,会执行该函数并传入两个参数dispatch 和getState
1、对每次派发的action 进行拦截,进行日志打印
- 这样修改意味着直接修改了dispatch 的调用过程
- 在调用dispatch 的过程中,真正调用的函数是dispatchAndLog
function patchLogging(store) {
let next = store.dispatch
function dispatchAndLog(action) {
console.log("dispatching:", action)
next(addAction(5))
console.log("新的state:", store.getState())
}
store.dispatch = dispatchAndLog;
}
2、redux-shunk 的原理
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
}
thunk(store)
3、applyMiddleware
function applyMiddleware(store, ...fns) {
fns.forEach(fn => {
fn(store)
})
}
export default appliMiddleware