文章已同步至掘金:https://juejin.cn/post/6844903983299952648
欢迎访问😃,有任何问题都可留言评论哦~
Redux 的工作流程(核心思想):
1.设计全局 state 的数据结构状态树
2.设计更改 state 数据、状态的 actionType 常量
3.根据 actionType,编写 actionCreator
4.根据各个 actionCreator 的返回值,用 reducer 做数据处理
5.有个 reducer 之后,用 createStore 来得到全局唯一的 store,来管理 state
6.用 bindActionCreator 函数将 actionCreator 和 store.dispatch 绑定起来,得到一组能更改 state 的函数
7.分发使用各个状态修改函数(dispatch)
源码剖析 {#%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90}
源码结构 {#%E6%BA%90%E7%A0%81%E7%BB%93%E6%9E%84}
src
├── utils #工具函数文件夹
├── applyMiddleware.js
├── bindActionCreators.js
├── combineReducers.js
├── compose.js
├── createStore.js
└── index.js #入口 js
index.js {#index.js}
index.js就是整个代码的入口:
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
function isCrushed() {}
if (
process.env.NODE_ENV !== 'production' \&\&
typeof isCrushed.name === 'string' \&\&
isCrushed.name !== 'isCrushed'
) {
warning(
'。。。'
)
}
`export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
}
`
这里的 isCrushed 函数主要是为了验证在非生产环境下 redux 是否被压缩(默认情况下,
isCrushed.name
等于isCrushed
,如果被压缩了,函数的名称会变短,一般会压缩成数字,那么 (isCrushed.name !== 'isCrushed'
) 就是true
),如果被压缩,就给开发者一个 warn 提示)。
然后就是暴露 createStore
、combineReducers
、bindActionCreators
、applyMiddleware
、compose
这几个接口给开发者使用。
createStore.js {#createstore.js}
createStore
是redux的核心API。
createStore
会生成一个仓库(store
),用来维护一个全局的state
。
import isPlainObject from 'lodash/isPlainObject'
import ?observable from 'symbol-observable'
// 私有 action
export var ActionTypes = {
INIT: '@@redux/INIT'
}
export default function createStore(reducer, preloadedState, enhancer) {
// 判断接受的参数个数,来指定 reducer 、 preloadedState 和 enhancer
if (typeof preloadedState === 'function' \&\& typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
// 如果 enhancer 存在并且适合合法的函数,那么调用 enhancer,并且终止当前函数执行
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
// 储存当前的 currentReducer
var currentReducer = reducer
// 储存当前的状态
var currentState = preloadedState
// 储存当前的监听函数列表
var currentListeners = \[\]
// 储存下一个监听函数列表
var nextListeners = currentListeners
var isDispatching = false
// 这个函数可以根据当前监听函数的列表生成新的下一个监听函数列表引用
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/\* 函数
... getState ...
... subscribe ...
... dispatch ...
... replaceReducer ...
... observable ...
\*/
dispatch({ type: ActionTypes.INIT })
`return {
dispatch,
subscribe,
getState,
replaceReducer,
[?observable]: observable
}
}
`
createStore
接收3个参数:
- reducer (处理函数,下面介绍)
- preloadedState(state的初始值)
- enhancer(一个高阶函数,可以改变store的接口,下面介绍)
createStore
的返回值是dispatch
、subscribe
、getState
、replaceReducer
、[?observable]: observable
,他们共同组成了一个store
。
action {#action}
action
代表的是用户的操作。
redux规定
action
一定要包含一个type
属性,且type
属性也要唯一,相同的type
,redux视为同一种操作,因为处理action
的函数reducer
只判断action
中的type
属性。
reducer {#reducer}
reducer 只是一个模式匹配的东西,真正处理数据的函数,一般是额外写在别的地方(当然直接写在reducer中也没问题,只是不利于后期维护),只是在reducer中调用罢了。
export default (state, action) => {
switch (action.type) {
case A:
return handleA(state)
case B:
return handleB(state)
case C:
return handleC(state)
default:
return state // 如果没有匹配上就直接返回原 state
}
}
reducer 接收两个参数,state 以及 action 函数返回的 action对象,并返回最新的 state。
reducer 为什么叫 reducer 呢?因为 action 对象各种各样,每种对应某个 case ,但最后都汇总到 state 对象中,从多到一,这是一个减少( reduce )的过程,所以完成这个过程的函数叫 reducer。
getState {#getstate}
function getState() {
return currentState
}
整个项目的currentState
是处于一个闭包之中,所以能一直存在,getState
会返回当前最新的state。
简单的说,就是类似于一个get
的方法,返回currentState
的值。
subscribe {#subscribe}
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
`}
}
`
subscribe
接收一个listener
。
他的作用是给store添加监听函数 。nextListeners
储存了整个监听函数列表。
subscribe
的返回值是一个unsubscribe
,是一个解绑函数,调用该解绑函数,会将已经添加的监听函数删除,该监听函数处于一个闭包之中,会一直存在,所以在解绑函数中能删除该监听函数。
dispatch {#dispatch}
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = currentListeners = nextListeners
for (let i = 0; i \< listeners.length; i++) {
const listener = listeners\[i\]
listener()
}
`return action
}
`
dispatch
接收一个参数action
。
代码会先调用createStore
传入的参数reducer
方法,reducer
接收当前state
和action
,通过判断actionType
,来做对应的操作,并返回最新的currentState
。
dispatch
还会触发整个监听函数列表,所以最后整个监听函数列表都会按顺序执行一遍。
dispatch
返回值就是传入的action
。
replaceReducer {#replacereducer}
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
`currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}
`
replaceReducer
是替换当前的reducer
的函数。
replaceReducer
接收一个新的reducer
,替换完成之后,会执行 dispatch({ type: ActionTypes.INIT })
,用来初始化store
的状态。
官方举出了三种replaceReducer
的使用场景,分别是:
- 当你的程序要进行代码分割的时候
- 当你要动态的加载不同的reducer的时候
- 当你要实现一个实时reloading机制的时候
combineReducers.js {#combinereducers.js}
// 以下只留下了核心代码
// combination 函数是 combineReducers(reducers) 的返回值,它是真正的 rootReducer
// finalReducers 是 combineReducers(reducers) 的 reducers 对象去掉非函数属性的产物
// mapValue 把 finalReducers 对象里的函数,映射到相同 key 值的新对象中
`function combination(state = defaultState, action) {
var finalState = mapValues(finalReducers, (reducer, key) => {
var newState = reducer(state[key], action); //这里调用子 reducer
if (typeof newState === 'undefined') {
throw new Error(getErrorMessage(key, action));
}
return newState; //返回新的子 state
});
//...省略一些无关的代码
return finalState; //返回新 state
};
`
这个函数可以组合一组 reducers,然后返回一个新的 reducer。
随着整个项目越来越大,state 状态树也会越来越庞大,state的层级也会越来越深,当某个action.type
所对应的 case 要修改深层属性时,那样的话函数写起来就非常难看,所以必须在这个函数的头部验证 state 对象有没有那个属性。
combineReducers
实现方法也比较简单,它遍历传入的reducers
,返回一个新的reduce
r,这个新对象的 key
跟传入的reducers
一样,它的 value
则是传入的reducers
的不同key
对应的value
展开的{ key: value }
。
举个例子:
var reducers = {
todos: (state, action) { // 此处的 state 参数是全局 state.todos属性
switch (action.type) {...} // 返回的 new state 更新到全局 state.todos 属性中
},
activeFilter: (state, action) { // 拿到 state.activeFilter 作为此处的 state
switch (action.type) {...} // new state 更新到全局 state.activeFilter 属性中
}
}
var rootReducer = combineReducers(reducers)
combineReducers 内部会将 state.todos 属性作为 todos: (state, action) 的 state 参数传进去,通过 switch (action.type) 之后返回的 new state 也会更新到 state.todos 属性中;也会将 state.activeFilter 属性作为 activeFilter: (state, action) 的 state 参数传进去,通过 switch (action.type) 之后返回的 new state 也会更新到 state.activeFilter 属性中。
bindActionCreators.js {#bindactioncreators.js}
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
`export default function bindActionCreators(actionCreators, dispatch) {
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
`
bindActionCreators
的代码就是将actionCreator
和dispatch
联结在一起。
对于多个 actionCreator
,我们可以像reducers
一样,组织成一个 key/action
的组合。
由于很多情况下,action
是 actionCreator
返回的,实际上要这样调用 store.dispatch(actionCreator(...args))
很麻烦,只能再封装一层,通过反复组合,将嵌套的函数分离。
compose.js {#compose.js}
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs\[0\]
}
`return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
`
compose
调用了ES5的Array.prototype.reduce
方法,将形如fn(arg1)(arg2)(arg3)...
的柯里化函数按照顺序执行。
其传入的参数为函数数组,返回的为reduce从左到右合并后的新的函数,是一个类似于链式调用的过程。
funcs.reduce((a, b) => (...args) => a(b(...args)))
这句特别重要,组合函数的这部非常重要,我们发现...args
参数会依次的从右到左执行,比如将b(...args)
的执行结果,传入a
中作为参数继续执行。
applyMiddleware.js {#applymiddleware.js}
export default function applyMiddleware(...middlewares) {
return createStore => (reducer, initialState) => {
var store = createStore(reducer, initialState);
var dispatch = store.dispatch; //拿到真正的 dispatch
// 将最重要的两个方法 getState/dispatch 整合出来
var middlewareAPI = {
getState: store.getState,
dispatch: action => dispatch(action)
};
// 依次传递给 middleware,让它们有控制权
var chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain, dispatch); // 再组合出新的 dispatch
return {
...store,
dispatch
};
`};
}
`
applyMiddleware
就是中间件的意思。
applyMiddleware
接收中间件为参数,并返回一个以createStore
为参数的函数;
同时applyMiddleware
又是createStore
函数中的第三个参数,所以我们回到createStore
的代码,找到了:
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
`return enhancer(createStore)(reducer, preloadedState)
}
`
当createStore
中传了第三个参数的时候,会执行enhancer(createStore)(reducer, preloadedState)
,这是一个柯里化函数;
我们可以设想中间件的使用方法:const store = createStore( reducer, applyMiddleware([...中间件]))
。
applyMiddleware([...中间件])
的返回值是一个以createStore
为参数的函数,这个函数会在createStore
中执行,返回的函数也会继续执行,最后返回一个store
。
继续回到applyMiddleware
中,在返回store
之前,中间件将最重要的两个方法 getState/dispatch
整合出来,并传递给中间件使用,中间件处理完之后,返回一个新的dispatch
。
applyMiddleware
把中间件放在一个chain
数组中,并通过compose
方法(我们上面已经介绍过了),让每个中间件按照顺序一次传入dispatch
参数执行,再组合出新的 dispatch
。由此可见,每个中间件的格式都应该是接收一个{ dispatch, getState }
,返回一个(dispatch) => { return function(action) { ... }}
补充(结合React使用) {#%E8%A1%A5%E5%85%85(%E7%BB%93%E5%90%88react%E4%BD%BF%E7%94%A8)}
通常我们使用Redux的时候,都是React要使用
那么就避免不了把这两者联系起来,其实本质上Redux
和React
是没有任何联系的。
那么我们要用到依赖react-redux
react-redux {#react-redux}
就是把 react 和 redux 联系到一起。
react-redux
提供两个方法:connect
和Provider
。
connect {#connect}
connect
方法就是连接React
组件和Redux store
。
connect
实际上是一个高阶函数,返回一个新的已经与Redux store
连接的组件类。
例如:
const VisibleCounter = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
Counter 是 UI 组件,VisibleCounter就是由 react-redux
通过connect
方法自动生成的容器组件。
-
mapStateToProps
:从Redux状态树中提取需要的部分作为props传递给当前的组件。 -
mapDispatchToProps
:将需要绑定的响应事件(action)作为props传递到组件上(也可以不用写这个,直接把actions绑上去就好了)。
例如://需要引入actions,就是一系列的方法 const VisibleCounter = connect( mapStateToProps, actions )(Counter)
代码示例:
import React, { Component } from 'react';
import { connect } from 'react-redux'
import actions from '../store/actions/counter1'
class Counter1 extends Component {
constructor(props) {
super(props)
}
render() {
return (
\<\>
\<h1\>Counter1\</h1\>
\<p\>{this.props.number}\</p\>
\<button onClick={() =\> this.props.increment(2)}\>+\</button\>
\<button onClick={() =\> this.props.decrement(2)}\>-\</button\>
\</\>
)
}
}
// state 就是仓库中的状态
// function mapStateToProps(state){
// // number第1个number是指这个组件中的属性
// // state.number 指的是仓库中的number
// return {number:state.number}
// }
// let mapStateToProps = function(state){
// // number第1个number是指这个组件中的属性
// // state.number 指的是仓库中的number
// return {number:state.number}
// }
// ({number:state.number}) 如果返回一个对象,需要给穿上对象外面包一个小括号
// let mapStateToProps = state=\>({number:state.number})
//第一种写法------------------------------------------------
// function mapStateToProps(state) {
// return {
// number: state.counter1.number
// }
// }
// function mapDispatchToProps(dispatch) {
// return bindActionCreators(actions, dispatch)
// }
// export default connect(mapStateToProps,mapDispatchToProps)(Counter1);
//第二种写法------------------------------------------------
// let mapStateToProps = state=\>({number:state.counter1.number})
// let mapDispatchToProps = dispatch=\>bindActionCreators(actions,dispatch)
// export default connect(mapStateToProps,mapDispatchToProps)(Counter1);
//第三种写法------------------------------------------------
let mapStateToProps = state=\>({number:state.counter1.number})
`export default connect(mapStateToProps,actions)(Counter1);
`
其中引入的actions
就是一系列的方法:
import * as types from "../action-types"
function increment(payload) {
return { type: types.ADD1, payload }
}
function decrement(payload) {
return { type: types.SUB1, payload }
}
`export default { increment, decrement }
`
Provider {#provider}
Provider
可以实现store
的全局访问,将store
传给每个组件。
原理:使用
React
的context
,context
可以实现跨组件之间的传递。
使用时,我们需要在入口index.js
文件中引入使用,如下:
import React from "react"
import ReactDOM from "react-dom"
import App from './App'
//使用react-redux中的Provider
import { Provider } from 'react-redux'
import store from './store'
`ReactDOM.render(
// 别忘了包起来,并且把store绑上去,可以让所有的组件都可以使用仓库里的状态
<Provider store={store}>
<App></App>
</Provider>
, window.app)
`
并且我们需要使用其中的connect
方法实现数据和方法的映射,使用方式也非常简单
redux-thunk {#redux-thunk}
就是增强store.dispatch()
的功能,即可以在reducer
中进行一些异步的操作,可以派发多种类型的东西。
使用方式也非常简单,直接将thunk
中间件引入,放在applyMiddleware
方法之中即可。
redux-logger {#redux-logger}
可以输出日志信息
使用的时候需要通过createLogger
创建一个logger
,再把创建这个logger
放在applyMiddleware
方法之中即可。
import { createLogger } from 'redux-logger'
const logger = createLogger({
// ...options
})
`applyMiddleware(logger)
`