简述
目的
Redux是一个用于更好地在组件外部管理状态的东西。通过使用它,我们可以轻松地更新或者获取state而不需要关心当state发生变动之后,触发页面的渲染。
通过看源码,来弄清楚Redux究竟是怎么管理state的。这会给日常的编码带来一些启发。
准备
仓库地址: https://github.com/reduxjs/react-redux
- 修改rollup.config.js文件,给里面的所有output 加 sourcemap选项,比如这样
(有个小知识,rollup比webpack更适合打包这种小型的js库,所以看一些结构和功能更复杂的库的时候他们用的是webpack。)
output: {
file: 'lib/redux.js',
format: 'cjs',
indent: false,
sourcemap: true
},
-
就可以build出有.map文件的redux了
- 然后去examples里面找项目引用这个js
- 用http-server启动项目,我找的是
counter-vanilla
这个项目。它是一个简单的用原生js写的页面。
突然有个想法,如果用jquery去写项目,用redux来管理数据和页面渲染好像也ok。 -
在浏览器调试起来
Redux.createStore
function createStore(
reducer: Reducer,
preloadedState?: PreloadedState | StoreEnhancer,
enhancer?: StoreEnhancer
){
……
let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
let isDispatching = false
/**
* This makes a shallow copy of currentListeners so we can use
* nextListeners as a temporary list while dispatching.
*
* This prevents any bugs around consumers calling
* subscribe/unsubscribe in the middle of a dispatch.
*/
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/**
* Reads the state tree managed by the store.
*
* @returns The current state tree of your application.
*/
function getState(): S {
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 as S
}
function subscribe(listener: () => void) {
……
}
function dispatch(action: A) {
……
}
function replaceReducer(
nextReducer: Reducer
): Store, NewActions, StateExt, Ext> & Ext {
……
}
/**
* Interoperability point for observable/reactive libraries.
* @returns A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
……
}
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT } as A)
const store = ({
dispatch: dispatch as Dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
} as unknown) as Store, A, StateExt, Ext> & Ext
return store
}
- 这里就是定义一些会被store暴露出来的方法和这些方法公用的一些变量,比如currentReducer这种。
- 并且调用了一次dispatch来获取默认的state。也就是我们传入的那个reducer默认return的那个对象。
- createStore函数的最后,return了挂着这些方法的store对象出来。
store.subscribe
调试代码
function render() {
valueEl.innerHTML = store.getState().count.toString()
}
render()
store.subscribe(render)
源码
let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
let isDispatching = false
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function subscribe(listener: () => void) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api/store#subscribelistener for more details.'
)
}
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/store#subscribelistener for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
- subscribe接受一个函数称为listener,然后对nextListeners数组去添加这个函数。
- 特别地,在push之前调用了
ensureCanMutateNextListeners
。
这个函数的作用是通过slice获取currentListeners的副本赋值给nextListeners,新的listener是添加进这个array而不是当前的currentListeners。
这可以避免当消费者(consumer)在dispatch的时候调用subscribe而引发问题。 - 之后return了
unsubscribe
,用于注销这个listener。实现也很一目了然,即修改nextListeners。
store.dispatch
function dispatch(action: A) {
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
}
- 执行currentReducer之前,将isDispatching置为
true
。这个标志位为true会阻止调用getState、dispatch、subscribe、unsubscribe等方法来避免一些可能的问题。 - 执行currentReducer之后,再遍历执行nextListeners里的函数,就是subscribe注册的那些。
总结
同系列:
Redux源码阅读(二)——combineReducers
Redux源码阅读(三)——connect