本文适合已经会使用
redux的同学阅读。好吧,其实就是自己梳理写给自己看的,顺便分享一下~
最近在知乎上看到了一篇整理讨论Vuex、Flux、Redux、Redux-saga、Dva、MobX这些状态管理库的设计思想的文章,链接在此。
想到以前最开始学习React的时候,被React全家桶折磨的那么痛苦,虽然当时也看过别人分享过Redux的分享,也稍微看了一些源码,目前已经忘记了差不多了。虽然网上已经有太多的分享和整理,但是主要是想写给自己看,温故而知新~
Redux 的基本用法
抛开 react,Redux非常简单,以官方github上的示例为样板,稍微改变点东西,以方便我在本地进行调试,我直接把静态资源的redux.js下载到本地,然后进行代码调试。下面是最简单的使用示例:
const type = {
increment: 'INCREMENT',
decrement: 'DECREMENT',
}
const initialState = {
count: 0
}
const reducer = function (state = initialState, action) {
switch(action.type) {
case type.increment:
return {
...state,
count: state.count + 1
}
case type.decrement:
return {
...state,
count: state.count - 1
}
default:
return state
}
}
const store = Redux.createStore(reducer)
store.subscribe(() => console.log(store.getState()))
setTimeout(() => {
store.dispatch({ type: type.increment })
// 1
store.dispatch({ type: type.increment })
// 2
store.dispatch({ type: type.decrement })
// 1
}, 4000)
这个代码就是创建了一个reducer然后,使用createStore创建了一个store,然后监听了变化,只要变化就打印当前的state,最后在setTimeout中进行dispatch让state发生变化。
可以看到这里暂时还没有使用applyMiddleware函数,也就是没有使用中间件,这个放在后面说。
入口函数
先看看源码结构:
Redux这个变量上, 挂载了一些属性,可以从示例代码上看到,入口函数就是createStore了。首先看一下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'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
...
function isCrushed() {}
...
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
基本没有什么东西,引入了其他文件的导出,然后汇集一起导出了。
createStore
export default function createStore(reducer, preloadedState, enhancer) {
...
}
可以看到createStore函数,接受三个参数,第一个就是传入的reducer处理函数,第二个是预置的state,第三个用来对createStore函数进行增强的中间件等工具方法了。一般enhancer我们都是传入的是applyMiddleware函数的返回值。
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function.'
)
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
函数一进来就会判断第二参数和第三个参数的正确行,这里可以看到,如果第二参数传入的是函数,并且第三个参数没传,这里会把它当成enhancer。这就是我们常这样使用的方式:createStore(reducer, applyMiddleware(...)),只传入两个参数。再之后就是返回enhancer(createStore)(reducer, preloadedState)的结果,这个放在后面说。
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
let currentReducer = reducer // 当前的 reducer
let currentState = preloadedState // 当前的state,默认为 preloaderState,一般为 undefined
let currentListeners = [] // 监听变化的函数队列
let nextListeners = currentListeners // 变化之后,下一次的监听变化的函数队列
let isDispatching = false // 是否处于 Dispatching 状态
紧接着就是判断了reducer是否为函数,然后声明了几个变量,用来初始化。
中间代码会声明一些操作函数,包括我们常用的dispatch,这些函数基本上就是对上面声明的这些变量在进行操作。我们直接看最后面。
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[?observable]: observable
}
首先在返回之前,调用了dispatch函数,对state进行了初始化,传入了一个对象,包含对type的声明,也就是一个action。
dispatch 函数
整个 createStore直接执行的代码没几行,重要的动作在dispatch({ type: ActionTypes.INIT })这句上,看看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
}
- 首先判断传入的
action必须要是一个字面量对象。 - 再判断
action是否存在type属性。 - 判断是否有其他的
dispatch正在执行。 - 将
isDispatching设置为true,然后将当前的state和传入的action传入到reducer去执行,然后得到结果赋值给currentState。 - 执行完第4步,就代表
state已经修改完了,所以将isDispatching置为false。 - 将
currentListeners赋值为nextListeners,然后进行遍历,执行里面所有的监听回调函数,最后返回action。
逻辑比较简单,当我们调用dispatch({ type: ActionTypes.INIT })的时候,最终是将currentState和{ type: ActionTypes.INIT }传入到了我们实现的reducer函数里面。
const reducer = function (state = initialState, action) {
switch(action.type) {
case type.increment:
return {
...state,
count: state.count + 1
}
case type.decrement:
return {
...state,
count: state.count - 1
}
default:
return state
}
}
这时候,currentState为undefined,type为ActionTypes.INIT,根据代码,可以看到最终返回的是initialState。这就是初始的state的赋值场景了。
在我们的应用代码里面会去调用dispatch函数,其实每次都是直接调用了reducer然后去遍历执行了listeners队列。简单吧,很简单。根据源码反映,只要dispatch函数的执行,listeners就会被执行,并不是state变了才回去执行,由此可以看出redux的监听就是全量的调用,粒度好像有点大哦。
subscribe 函数
对状态进行监听(其实是对执行dispatch监听),是调用的subscribe函数,它还会返回一个值用来取消当前函数的监听。
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
...
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
- 判断 listener 是否为函数。
- 将当前的
isSubscribed标志,置为true。 - 调用
ensureCanMutateNextListeners函数对currentListeners进行浅拷贝,赋值给nextListeners。 - 将传入的
listener入nextListeners队列。 - 返回
unsubscribe函数,这个函数左右就是在nextListeners队列中找到listener然后进行删除。
getState 函数
直接将currentState变量的值进行了返回。
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
replaceReducer 函数
传入一个新的reducer函数,进行替换,最后调用一下dispatch函数,得到reducer对应的state。
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
// This action has a similiar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
dispatch({ type: ActionTypes.REPLACE })
}
observable 函数
这个函数,我没有使用过,暂时没有碰到相关的场景,根据源码,我理解为,类似于subscribe,但是这个监听回调可以结合符合约定的第三方observable库。比如源码注释提到的https://github.com/tc39/proposal-observable。
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
}
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[?observable]() {
return this
}
}
}
小结
以上梳理了没有使用中间件增强的基本流程,让我最深刻的就只有listener的调用,只是和dispatch调用有关,至于state有没有修改只是简单赋值而已。
redux应用了很多函数式的编程技巧,就比如subscribe函数最后直接返回取消订阅的函数。
未完待续~