接着前面的,我們繼續,打開createStore.js, 直接看最後, createStore傳回的就是一個帶着5個方法的對象。
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
同樣的,我先删除一些不需要的代碼,簡化成如下, 注意看備注。(注:這裡先無視中間件和enhancer,後篇再說)
export const ActionTypes = {
INIT: '@@redux/INIT'
}
export default function createStore(reducer, preloadedState, enhancer) {
// 初始化參數
let currentReducer = reducer // 整個reducer
let currentState = preloadedState //目前的state, getState傳回的值就是他,
let currentListeners = [] // 目前的訂閱,搭配 nextListeners
let nextListeners = currentListeners //下一次的訂閱 ,搭配currentListeners
let isDispatching = false //是否處于 dispatch action 狀态中
// 内部方法
function ensureCanMutateNextListeners() { } // 確定currentListeners 和 nextListeners 是不同的引用
function getState() { } // 獲得目前的狀态,傳回的就是currentState
function subscribe(listener) { } //訂閱監聽,傳回一個函數,執行該函數,取消監聽
function dispatch(action) { } // dispacth action
function replaceReducer(nextReducer) { } // 替換 reducer
function observable() { } //不知道哈哈
//初始化state
dispatch({ type: ActionTypes.INIT })
//傳回方法
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
ensureCanMutateNextListeners
這個方法主要用在 subscribe裡面,
- 在每次訂閱和取消訂閱的時候,會讓 nextListeners 和 currentListeners 不是同一個引用,
- 在每次 dispatch的時候,當 reducer執行完畢,訂閱執行前,讓 nextListeners 和 currentListeners 是一個引用
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
為什麼這麼設計,在subscribe方法上有很詳細的注解,我的了解是假如訂閱在執行過程中,這裡說的是訂閱執行過程,不是reducer執行過程
有新加的訂閱添加的時候,新的訂閱是不會被執行的,因為是一份拷貝
有新的訂閱删除的時候,被删除的還是會執行的。
簡單說,就是新的删除和添加,下次生效。
getState
就是傳回利用閉包存的currentState
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
return currentState
}
subscribe
添加訂閱
- 每次添加前,如果 nextListeners 和 currentListeners 是一個引用,重新複制一個
- 并存入 nextListeners
- 傳回一個函數,執行該函數取消訂閱,
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) // 從nextListeners裡面删除,下次dispatch會生效
}
}
dispatch
派發一個action,讓reducer更新資料,下面都有注釋了,為啥可說的。
- 如果上一次派發沒完畢,接着派發是會出異常的,對于單線程的js來說倒是沒啥大問題
function dispatch(action) {
if (!isPlainObject(action)) { // action 必須是對象
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') { // 必須有type屬性
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 // 讓nextListeners生效
for (let i = 0; i < listeners.length; i++) { // 挨個執行訂閱
const listener = listeners[i]
listener()
}
return action // 傳回action
}
replaceReducer
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') { // 不是函數,抛出異常
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer // 替換reducer
dispatch({ type: ActionTypes.INIT }) // 重新初始化
}
observable 還沒啥研究,暫不說了。
最後的代碼為,
- 初始化 state
- 傳回相關方法
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
這裡說一下 dispatch({ type: ActionTypes.INIT }) 是怎麼達到初始化state的,
我們再回頭看一下disptach中的一段代碼
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
這裡先講非合成的reducer,帶合成的後面說。
createStore的第一個參數為 reducer,第二個為初始化state的預設值,
- 如果你傳入了第二個參數,currentState就等于你傳入的值,而執行一次 dispatch的時候,系統定義的 type 為@@redux/INIT的action,你肯定是沒有定義的額,看下面代碼,就會直接傳回state, 那麼執行 currentReducer(currentState, action) 得到的結果還是 currentState
- 如果你沒有傳入第二個參數,在reducer的第一個參數指定了預設值,那麼reducer處理type為 @@redux/INIT的action的時候,傳回的就是reducer第一個參數 state的預設值,然後被指派給了currentState
- 如果沒有傳入第二個參數,同時reducer的state也沒指定值,那麼,你的dispatch一般都會報錯,因為你的state從開始就是undefined
- 如果recuder函數和createStore都設定了預設了,那麼reducer的預設值是不會生效的
let todoReducer = function (state = todoList, action) {
switch (action.type) {
case 'add':
return [...state, action.todo]
case 'delete':
return state.filter(todo => todo.id !== action.id)
default:
return state
}
}
這裡相對特别的是 合成recuder,後面再說。
到此為止,你隻用redux的createStore方法,就能完成資料控制了,combineReducers,bindActionCreators,applyMiddleware,compose 都隻是對redux的增強。
再回頭看看我們第一篇提到的代碼:(雲淡風輕)
- 初始化的state在recuder指派,和在createStore指派是等價的,都指派的話,createStore的指派會生效。 (createStore用的是顯示指派, reducer:預設參數)
/* 簡單示例 */
let { createStore } = self.Redux
//預設state
let todoList = []
// reducer
let todoReducer = function (state, action) {
switch (action.type) {
case 'add':
return [...state, action.todo]
case 'delete':
return state.filter(todo => todo.id !== action.id)
default:
return state
}
}
//建立store
let store = createStore(todoReducer,todoList)
//訂閱
function subscribe1Fn() {
console.log(store.getState())
}
let sub = store.subscribe(subscribe1Fn)
store.dispatch({
type: 'add',
todo: {
id: 1,
content: '學習redux'
}
})
store.dispatch({
type: 'add',
todo: {
id: 2,
content: '吃飯睡覺'
}
})
store.dispatch({
type: 'delete',
id: 2
})
// 取消訂閱
sub()
console.log('取消訂閱後:')
store.dispatch({
type: 'add',
todo: {
id: 3,
content: '打遊戲'
}
})