〖Redux〗、〖ReduxToolkit〗,你们好呀!
redux和React没有直接关系,完全可以在React、Angular或其它地方单独使用Redux;
但React和Redux结合的更好,通过state去描述界面状态;
三大原则
单一数据源
- 整个应用程序的state被存储在一棵object tree中,并且object tree只存储在一个store中;
- Redux并没有强制不能创建多个store,但那样不利于数据维护;
- 单一数据源可以让整个应用程序的state变得方便维护、追踪、修改;
state是只读的
- 唯一修改state的方法一定是派发(dispatch)action,不可直接修改state;
- 这样就确保了视图或网络请求都不能直接修改state,他们只能通过action来描述自己想要如何修改state;
- 这样可以保证所有的修改都被集中化处理,并按照严格的顺序来执行,无需担心race condition(竞态) 的问题;
使用纯函数来执行修改
- 通过reducer将旧的state和action联系到一起,并返回一个新的state;
- 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducer,分别操作不同的state tree的一部分;
- 但所有的reducer都应该是纯函数,不能产生任何副作用;
核心
store
action
需要通过action来更新数据:
- 所有的状态变化,必须通过派发(dispatch)action来更新;
- action是一个普通的js对象,用来描述此次更新的type和content;
reducer
如何将state和action联系在一起呢?
reducer是个纯函数;
reducer做的事情是将旧的state和action结合起来生成一个新的state;
基本使用
import { createStore } from 'redux'
// 初始化数据
const initialState = {
name: 'zsf',
counter: 100
}
// 定义reducer函数
function reducer(state = initialState, action) {
// 有数据进行更新时,返回一个新的state
// 没数据更新返回旧的state
return state
}
// 创建store
export const store = createStore(reducer)
使用store.getState() 就可以获取state了;
reducer接收两个参数:
- 1)旧的state
- 2)action
修改state
一旦调用dispatch,reducer就会重新执行;
import { createStore } from 'redux'
// 初始化数据
const initialState = {
name: 'zsf',
counter: 100
}
// 定义reducer函数
function reducer(state = initialState, action) {
// 有数据进行更新时,返回一个新的state
if (action.type === 'change_name') {
return { ...state, name: action.name }
}
// 没数据更新返回旧的state
return state
}
// 创建store
export const store = createStore(reducer)
当action类型过多时,应该使用switch替代if
console.log(store.getState()) // { name: 'zsf', couter: 100 }
const nameAction = { type: 'change_name', name: 'hhh' }
store.dispatch(nameAction)
console.log(store.getState()) // { name: 'hhh', couter: 100 }
订阅state
上述获取state要手动,想要自动获取最新state的话,需要订阅;
store.subscribe() 传入一个回调函数,当state发生变化时会回调该函数;
store.subscribe(() => {
console.log(store.getState())
})
取消订阅
store.subscribe()返回值是个函数,调用即可取消订阅;
动态生成action
store.dispatch({ type: 'change_name', name: 'hhh' })
store.dispatch({ type: 'change_name', name: 'xxx' })
store.dispatch({ type: 'change_name', name: 'jjj' })
你会发现有重复的内容,开发中会使用一个叫actionCreators的东西,它会帮助我们创建action;
const changeNameAction = (name) => ({
type: 'change_name',
name
})
store.dispatch(changeNameAction('hhh'))
store.dispatch(changeNameAction('xxx'))
store.dispatch(changeNameAction('jjj'))
一般会将类似上述changeNameAction的函数放到store下actionCreators.js文件中
constants.js
对于两处地方要使用一样的字符串,应该写成常量,放constants.js中;
代码组织原则
将派发的action生成过程放到一个actionCreators函数中,并将这些函数放到actionCreators.js文件中;
actionCreators和reducer函数中使用的字符串常量是一致的,将常量抽取到constants.js文件中;
将reducer和默认值(initialState)放到reducer.js文件中,而不是在index.js;
在项目中使用
在类组件中的componentDidMount生命周期中订阅state中的数据;
假设使用了counter
componentDidMount() {
store.subscribe(() => {
const state = store.getState()
this.setState({ counter: state.counter })
})
}
并且在组件卸载后也要手动在componentWillUnmount生命周期进行取消订阅;
如果每个组件都要进行这些操作,那重复可就太多了;
可以把这些重复的操作抽取到高阶组件;
真实开发会使用一个库,react-redux;
由于可能多个组件都会使用到store中的数据,所以可以给根组件提供;
根组件
...
import { Provider } from 'react-redux'
import { store } from './store'
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(
<Provider store={store}>
<App/>
</Provider>
)
假设一个About组件想使用store中的数据,可以使用react-redux中的connect;
connect的返回值是个高阶组件;
About
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
export class About extends PureComponent {
render () {
return (
<div>About</div>
)
}
}
export default connect()(About)
connect为什么不直接封装成高阶组件,而返回值才是高阶组件?
connect参数接收两个函数;
第一个函数需要告知connect拦截组件(上述的About)需要用store中的哪些数据,不是全部都要使用;
第一个函数返回的是一个映射,也就是当前组件和store中哪些数据的映射;
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
export class About extends PureComponent {
render () {
return (
<div>About</div>
)
}
}
function fn1 (state) {
return {
counter: state.counter
}
}
export default connect(fn1)(About)
connect内部原理
connect内部做了什么呢?
做了类似这样的操作
<About {...this.props} {...obj} />
会将上述fn1返回的对象(obj)和接收到的props以属性的方式传给About组件;
这不就是高阶组件应用场景之一的-----props增强吗?
同样,高阶组件另外的应用场景----拦截生命周期也在connect内部应用了(需要对store中的state订阅和取消订阅);
由于About需要使用的数据已经注入到props中了,使用方式不就和使用props一样了吗?
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
export class About extends PureComponent {
render () {
const { counter } = this.props
return (
<div>
<h2>About----{counter}</h2>
</div>
)
}
}
function fn1 (state) {
return {
counter: state.counter
}
}
export default connect(fn1)(About)
一般也不会使用fn1这样的函数命名,而是换成mapStateToProps这样见明知义的命名;
修改状态
如果About组件想修改counter这个状态,该怎么操作?
通常是导入store,然后使用store.dispatch() 派发action;
而派发操作是可以不在组件内进行的,若不希望这派发操作与组件耦合在一起,是可以解耦的;
上面说到connect是接收两个函数的,第二个函数fn2接收一个参数(store传来的dispatch);
函数fn2返回的也是个映射,是当前组件修改状态和store中修改状态的映射( 下面的addNumber与addNumberAction);
而修改状态的addNumber就添加到当前组件的props里面了,就可以通过this.props.addNumber进行派发action操作了;
而这样去派发,代码都是组件自己的行为,就可以解耦了;
About
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { addNumberAction } from './store/index'
export class About extends PureComponent {
changeNum (num) {
this.props.addNumber(num)
}
render () {
const { counter } = this.props
return (
<div>
<h2>About----{counter}</h2>
<button onClick={e => this.changeNum(6)}>+6</button>
</div>
)
}
}
function fn1 (state) {
return {
counter: state.counter
}
}
function fn2 (disptch) {
return {
addNumber (num) {
disptch(addNumberAction(num))
}
}
}
export default connect(fn1, fn2)(About)
一般也不会使用fn2这样的函数命名,而是换成mapDispatchToProps这样见明知义的命名;
分模块
如果store中分模块,每个模块都有
- constant.js
- actionCreators.js
- reducer.js
- index.js
那得需要将每个模块的reducer结合在一起,创建store的时候传进去;
可以使用redux中的combineReducers,接收一个对象,属性名任意起,而属性的值分别是模块的reducer;
const reducer = combineReducers({
counter: counterReducer,
home: homeReducer
})
redux分模块的好处是,方便修改自己负责的逻辑,防止出现代码冲突;
如果整个应用程序需要共享的状态管理不分开,容易出现误改他人代码;
combineReducers是如何实现的呢?
它将传入的reducer合并到一个对象中,最终返回一个combination的函数(相当于之前的reducer函数);
执行combination函数的过程中,它会通过判断前后的数据是否相同来决定返回之前的state还是新的state;
新的state会触发订阅者发生对应的刷新,而旧的state可以有效的组织订阅者发生刷新;
function combineReducers(state = {}, action) {
// 返回一个对象,store的state
return {
counter: counterReducer(state.counter, action)
}
}
组件中的异步操作
上述使用的状态是本地的数据,但在真实开发往往是下进行异步的网络请求,然后再保存到redux中;
比如在Home组件请求了一些数据,如果Home想使用,只需要将这些数据保存到自己state中,使用render进行展示即可;
而About组件也想使用那些数据的话,怎么办呢?
- 首先可以想办法一步步传递过去,比如放到Context里面去,然后做一个共享,但是这个步骤有点繁琐,排除;
- 可以使用eventBus,拿到Home数据之后发射出去一个全局事件,因为eventBus是可以传参数的,可以把请求到数据传递过来,但是在整个项目中过多使用eventBus,监听的事件来自哪里是很难把控的,当出现bug是时不好调试,并且eventBus不建议传递这么大的数据,排除;
- 最合适的方法是redux,全局共享一个store,里面的state保存网络请求到数据;
那如何保存在redux中呢?
假如在actionCreators.js文件中
export const getHomeAction = () => {
axios.get('地址').then((res) => {
const banners = res.data.banners.list
})
return {
type: 'change_banners',
banners: banners
}
}
这样的话,不确定then什么时候被回调,banners也就不确定有没有值;
你可能会说,可以拿到结果之后再return,比如这样
export const getHomeAction = () => {
axios.get('地址').then((res) => {
const banners = res.data.banners.list
return {
type: 'change_banners',
banners: banners
}
})
}
这样的话,return的内容已经不是getHomeAction所return的了;
应该这样做
组件中
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { getHomeAction } from './store/index'
export class About extends PureComponent {
getHomeData() {
this.props.getHome()
}
render () {
const { counter } = this.props
return (
<div>
<h2>About----{counter}</h2>
</div>
)
}
}
function mapStateToProps(state) {
return {
counter: state.counter
}
}
function mapDispatchToProps(disptch) {
return {
getHome() {
disptch(getHomeAction())
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(About)
在actionCreators.js文件中
export const getHomeAction = () => {
return {}
}
如果是一个普通的action,那需要返回action对象;
而问题是,对象中不能直接拿到从服务器请求到的异步数据,所以不能直接返回对象;
应该返回一个函数,然后函数里进行网络请求;
而redux要求派发的是action对象,要想派发函数,可以使用一个中间件----redux-thunk对store进行增强,使其可以派发函数;
redux-thunk
安装
npm install redux-thunk
然后使用redux中的applyMiddleware,在store创建的时候,传递第二个参数
import { createStore, applyMiddleware } from 'redux'
improt thunk from 'redux-thunk'
import reducer from './reducer'
const store = createStore(reducer, applyMiddleware(thunk))
export default store
thunk内部做了什么?
检测到派发的是函数,就自动执行,且可以接收两个参数:
- 第一个是store的dispatch(用于之后再次派发action);
- 第二个是store的getState(考虑到之后的操作依赖原来的状态);
在actionCreators.js文件中
export const getHomeAction = () => {
function foo(dispatch) {
axios.get('地址').then((res) => {
const banners = res.data.banners.list
dispatch({ type: 'change_num', banners })
})
}
return foo
}
两个工具
- redux devtool
- react devtool
谷歌商店或github下载(版本有点低)
官方建议redux devtool在生产环境是看不到state的,所以默认情况直接使用redux devtool是查看不到状态的,开发环境才开启(有的网站生产环境时开启的,这不好);
在redux的index.js
import { compose } from "redux"
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)))
生产环境的话,改成
import { compose } from "redux"
const composeEnhancers = compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)))
ReduxToolKit
简称RTK
官方推荐使用ReduxToolKit工具包来进行Redux相关管理,编写代码会方便许多(创建store方便,使用起来差不多);
安装
npm install @redux/toolkit react-redux
由于这工具包也是针对react-redux进行了封装,所以两个都需要安装;
核心API
- configureStore
- createSlice
- createAsyncThunk
configureStore:封装createStore以提供简化的配置选项和良好的默认值。它可以自动组合你的slice reducer,添加任何的Redux中间件,redux-thunk默认包含,并启用Redux DevTools Extension;
createSlice:接受reducer函数的对象,切片名称和初识状态值,并自动生成切片reducer,并带有相应的actions;
createAsyncThunk:接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fullfilled/rejected基于该承诺分派动作类型的thunk;
基本使用
configureStore用于创建store对象,常见参数如下:
- reducer,将各个slice中的reducer组成一个对象;
- middleware,传入其它中间件;
- devtools: 是否配置devtools工具,默认为true;
store文件夹下的index.js
import { configureStore } from '@reduxjs/toolkit'
const store = configStore({
render: {}
})
export default store
createSlice接收以下参数:
- name
- initialState
- reducers
name:用户标记slice的名称(在redux-devtool会显示对应的名称),作用类似于action对象的type属性;
initialState:初始化值;
reducers:
- 相当于之前的reducer函数,是对象,可以添加更多函数;
- 函数类似于原来reducer中的一个case语句;
- 函数的参数(state,调用这个action时传递的action参数)
而导出时,只需要将createSlice创建出切片中的reducer导出,后续会将各个切片的reducer结合成一个;
createSlice返回一个对象,包含所有的actions;
某个store模块
import { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initlState: {
counter: 100
},
reducers: {
addNumber(state, action) {
},
}
})
export default counterSlice.reducer
store文件夹下的index.js
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './features/counter'
const store = configStore({
render: {
counter: createReducer
}
})
export default store
发现没有,创建一个store是不是方便许多?
接着使用counter模块的状态,与上述使用方式差不多
根组件
...
import { Provider } from 'react-redux'
import { store } from './store'
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(
<Provider store={store}>
<App/>
</Provider>
)
App
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
export class App extends PureComponent {
render () {
const { counter } = this.props
return (
<div>
<h2>App----{counter}</h2>
</div>
)
}
}
function mapStateToProps(state) {
return {
counter: state.counter.counter
}
}
export default connect(mapStateToProps)(App)
修改状态
App
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { addNumberAction } from './store/couter'
export class App extends PureComponent {
addNum(num) {
this.props.addNumber(num)
}
render () {
const { counter } = this.props
return (
<div>
<h2>App----{counter}</h2>
<button onClick={ e => this.addNum(1) }>+1</button>
</div>
)
}
}
function mapStateToProps(state) {
return {
counter: state.counter.counter
}
}
function mapDispatchToProps(dispatch) {
return {
addNumber(num) {
dispatch(addNumberAction(num))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
某个store模块(couter)
import { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initlState: {
counter: 100
},
reducers: {
addNumber(state, { payload }) {
state.counter += payload
},
}
})
export const { addNumberAction } = counterSlice.actions
export default counterSlice.reducer
看到这段代码
reducers: {
addNumber(state, { payload }) {
state.counter += payload
},
}
你可能会问:为什么不像reducer那样,返回新的state?
其实这里RTK也做了优化,它使用了一个库(immer.js),只要修改了state,就会创建新的state然后返回;
异步操作
在之前的开发中,通过redux-thunk中间件让dispatch中可以进行异步操作;
Redux Toolkit默认已经集成了Thunk相关功能:createAsyncThunk
假设Home组件发起网络请求获取到的数据,About组件也想使用
store某模块(home)
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
export const getHomeAction = createAsyncThunk('getHomeData', async () => {
const res = await axios.get('地址')
})
const homeSlice = createSlice({
name: 'home',
initlState: {
banners: []
},
reducers: {
changeBanners(state, { payload }) {
state.banners += payload
},
}
})
export default homeSlice.reducer
如何将在getHomeAction中获取的数据,放到homeSlice中的initlState呢?
createAsyncThunk的创建出来的action被dispatch时,会存在三种状态:
- pending:action被发出,但还没有最终结果;
- fulfilled:获取到最终结果(有返回值的结果);
- rejected:执行过程中有错误或抛出异常;
可以在createSlice中的extraReducer中监听这些结果:
store某模块(home)
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
export const getHomeAction = createAsyncThunk('getHomeData', async () => {
const res = await axios.get('地址')
return res.data
})
const homeSlice = createSlice({
name: 'home',
initlState: {
banners: []
},
reducers: {
changeBanners(state, { payload }) {
state.banners = payload
},
},
extraReducers: {
[getHomeAction.fulfilled](state, { payload }) {
state.banners = payload.data.banners.list
}
}
})
export default homeSlice.reducer
其中pedding和rejected状态可以不监听
Home组件
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { getHomeAction } from './store/home'
export class Home extends PureComponent {
componentDidMount() {
this.props.getHomeData()
}
render () {
return (
<div>
<h2>Home</h2>
</div>
)
}
}
function mapDispatchToProps(dispatch) {
return {
getHomeData() {
dispatch(getHomeAction())
}
}
}
export default connect(null, mapDispatchToProps)(Home)
数据不可变性
无论是类组件的state还是redux中管理的state,都强调数据的不可变性;
整个js编码中,数据的不可变性非常重要;
所以前面进常进行浅拷贝来完成某些操作,但浅拷贝事实上也是存在问题的;
- 比如过大的对象,浅拷贝也会造成性能的浪费;
- 浅拷贝后的对象,在深层改变时,依然会对之前的对象产生影响;
redux toolkit使用了immerjs这个库保证了数据的不可变性
为了节省内存,当数据被修改时,会返回一个对象,但新的对象会尽可能利用之前的数据结构而不会对内存造成浪费;
实现connect
实现关键点:
- 两个参数为函数
- 返回一个高阶组件
- 获取到store的state作为属性传进需要共享该state的组件
- dispatch同理
- 当state中被共享的数据发生更新时,组件的render重新执行,需要监听订阅的state是否发生改变
- 取消state的订阅
- 降低对store的耦合度
参数
首先,它接收两个函数作为参数
function connect(mapStateToProps, mapDispatchToProps) {
}
返回值
返回一个高阶组件
import { PureComponent } from 'react'
export default function connect(mapStateToProps, mapDispatchToProps) {
return function(WrapperComponent) {
class Newcpn extends PureComponent {
render() {
return <WrapperComponent {...this.props} />
}
}
return Newcpn
}
}
获取到store的state作为属性传进WrapperComponent
调用第一个参数,并传进state,它的返回值就是一个映射,就是想共享store中哪些数据;
然后将该映射注入WrapperComponent的props
同理,第二个参数也是如此
import { PureComponent } from 'react'
import store from './store'
export default function connect(mapStateToProps, mapDispatchToProps) {
return function(WrapperComponent) {
class Newcpn extends PureComponent {
render() {
const stateObj = mapStateToProps(store.getState())
const dispatchObj = mapDispatchToProps(store.dispatch)
return <WrapperComponent {...this.props} {...stateObj} {...dispatchObj}/>
}
}
return Newcpn
}
}
状态更新
目前还存在一个问题:当WrapperComponent修改共享的state时,界面没更新。
应该根据最新状态重新执行WrapperComponent的render函数
import { PureComponent } from 'react'
import store from './store'
export default function connect(mapStateToProps, mapDispatchToProps) {
return function(WrapperComponent) {
class Newcpn extends PureComponent {
componentDidMount() {
// 监听到订阅的state发生改变,强制执行render函数
store.subscribe(() => {
this.forceUpdate()
})
}
render() {
const stateObj = mapStateToProps(store.getState())
const dispatchObj = mapDispatchToProps(store.dispatch)
return <WrapperComponent {...this.props} {...stateObj} {...dispatchObj}/>
}
}
return Newcpn
}
}
但这样不好:state中任何改变,都会重新执行WrapperComponent的render函数;
应该是WrapperComponent用到的那些state发生改变,才需要重新执行render函数
import { PureComponent } from 'react'
import store from './store'
export default function connect(mapStateToProps, mapDispatchToProps) {
return function(WrapperComponent) {
class Newcpn extends PureComponent {
constructor(props) {
super(props)
this.state = mapStateToProps(store.getState())
}
componentDidMount() {
// 监听到订阅的state发生改变,强制执行render函数
store.subscribe(() => {
// this.forceUpdate()
this.setState(mapStateToProps(store.getState()))
})
}
render() {
const stateObj = mapStateToProps(store.getState())
const dispatchObj = mapDispatchToProps(store.dispatch)
return <WrapperComponent {...this.props} {...stateObj} {...dispatchObj}/>
}
}
return Newcpn
}
}
取消订阅
不要忘记组件卸载时取消state的订阅哦
import { PureComponent } from 'react'
import store from './store'
export default function connect(mapStateToProps, mapDispatchToProps) {
return function(WrapperComponent) {
class Newcpn extends PureComponent {
constructor(props) {
super(props)
this.state = mapStateToProps(store.getState())
}
componentDidMount() {
// 监听到订阅的state发生改变,强制执行render函数
this.unsubscribe = store.subscribe(() => {
// this.forceUpdate()
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 Newcpn
}
}
解耦
目前还有一个问题:与上一层目录的store耦合了
- 方式一,connect再接收一个参数,让使用者传入store;
- 方式二,再提供一个StoreContext;
方式一会每次使用connect时会多传一个参数,有点麻烦
StoreContext.js
import { createContext } from 'react'
export const StoreContext = createContext()
统一导出
export { StoreContext } from './StoreContext'
export { connect } from './connect'
使用者
import { StoreContext } from '目录名称'
<StoreContext.Provider value={store}>
<使用者 />
</StoreContext.Provider>
connect.js
import { PureComponent } from 'react'
export { StoreContext } from './StoreContext'
export function connect(mapStateToProps, mapDispatchToProps) {
return function(WrapperComponent) {
class Newcpn extends PureComponent {
constructor(props, context) {
super(props)
this.state = mapStateToProps(context.getState())
}
componentDidMount() {
// 监听到订阅的state发生改变,强制执行render函数
this.unsubscribe = context.subscribe(() => {
// this.forceUpdate()
this.setState(mapStateToProps(this.context.getState()))
})
}
componentWillUnmount() {
this.unsubscribe()
}
render() {
const stateObj = mapStateToProps(this.context.getState())
const dispatchObj = mapDispatchToProps(this.context.dispatch)
return <WrapperComponent {...this.props} {...stateObj} {...dispatchObj}/>
}
}
Newcpn.contextType = StoreContext
return Newcpn
}
}
中间件原理
打印日志
先看一个需求:打印dispatch日志
- dispatch之前,打印action
- dispatch结束之后,打印state的结果
直接的思路是这样的
const action = addNumberAction()
console.log('派发一个action', action)
dispatch(action)
console.log(store.getState())
这样有个问题:每次派发都需要这样写,重复代码太多
可以编写一个中间件,让中间件完成这个过程
派发action之前和之后,可以让中间件做一个拦截(正常情况是一派发就交给reducer)
上述redux-thunk中间件所做的事情是:
检测dispatch传入的是否为对象,若是函数则立即执行,并传入dispatch和state
function log(store) {
// 修改之前先记录原来的dispatch
const next = store.dispacth
function logAndDispatch(action) {
console.log('当前派发的action', action)
// 真正派发的代码:使用之前的dispatch派发
next(action)
console.log('派发之后的结果', store.getState())
}
store.dispatch = logAndDispatch
}
log(store)
store.dispatch({ type: 'addNumber', num: 100 })
将原来store.dispatch改成了logAndDispatch;
这种技术叫monkey patch(猴补丁,篡改现有代码,对整体的执行逻辑进行修改)
这便是中间件的原理
实现thunk核心代码
function thunk(store) {
// 修改之前先记录原来的dispatch
const next = store.dispacth
function dispatchThunk(action) {
// 如果派发的是函数
if (typeof action === 'funcion') {
// 如果派发函数里面又派发了一个函数,使用原来的dispatch会报错,所以应该使用新的dispatch
action(store.diapatch, store.getState)
} else {
next(action)
}
}
store.dispatch = dispatchThunk
}
thunk(store)
store.dispatch(function(dispatch, getState) {
dispatch({})
})
实现applyMiddleware核心代码
function applyMiddleware(store, ...fns) {
fns.forEach(fn => {
fn(store)
})
}
状态管理选择
react中如何管理状态?
主要有三种:
- 组件内部自己的state;
- Context数据的共享;
- Redux管理应用状态;
开发中该如何选择呢?
redux作者的建议:
- UI相关的组件内部可以维护的状态,在组件内部自己来维护;
- 大部分需要共享的状态,都交给redux来管理和维护;
- 从服务器请求的数据(包括请求的操作),交给redux来维护;
转载自:https://juejin.cn/post/7237856777588490297