在以前的文章redux從入門到實踐當中對redux
的使用進行了說明,此次就來看下它的源碼,從而進一步的熟悉它。node
相關git地址react
git clone https://github.com/reduxjs/redux.git
複製代碼
構建文檔是CONTRBUTING.md
webpack
"main": "lib/redux.js",
// ...
"scripts": {
"clean": "rimraf lib dist es coverage",
"format": "prettier --write \"{src,test}/**/*.{js,ts}\" index.d.ts \"**/*.md\"",
"format:check": "prettier --list-different \"{src,test}/**/*.{js,ts}\" index.d.ts \"**/*.md\"",
"lint": "eslint src test",
"pretest": "npm run build",
"test": "jest",
"test:watch": "npm test -- --watch",
"test:cov": "npm test -- --coverage",
"build": "rollup -c",
"prepare": "npm run clean && npm run format:check && npm run lint && npm test",
"examples:lint": "eslint examples",
"examples:test": "cross-env CI=true babel-node examples/testAll.js"
}
複製代碼
從package.json
當中能夠看到redux
的入口文件是lib/redux.js
,這個文件是經過打包出來的。那咱們看下打包配置文件rollup.config
git
{
input: 'src/index.js',
output: { file: 'lib/redux.js', format: 'cjs', indent: false },
external: [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {})
],
plugins: [babel()]
},
// ...省略
複製代碼
能夠看到入口文件應該是src/index.js
github
咱們來看下src/index.js
web
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'
/* * This is a dummy function to check if the function name has been altered by minification. * If the function has been minified and NODE_ENV !== 'production', warn the user. */
// 是否壓縮代碼,若是運行環境在非生成環境可是代碼被壓縮了,警告用戶
function isCrushed() {}
// 判斷環境是不是生成環境,若是是生成環境使用此代碼就給出警告提示
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
) {
warning(
'You are currently using minified code outside of NODE_ENV === "production". ' +
'This means that you are running a slower development build of Redux. ' +
'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
'to ensure you have the correct code for your production build.'
)
}
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
複製代碼
src/index.js
主要是將方法暴露出來,給使用者使用npm
createStore
用於建立storecombineReducers
用於組合成rootReducers,由於在外部初始化store時,只能傳入一個reducersbindActionCreators
組裝了dispatch方法applyMiddleware
合併多箇中間件compose
將中間件(middleware)和加強器(enhancer)合併傳入到createStore
中src/combineReducers.js
json
export default function combineReducers(reducers) {
// 遍歷出reducers的對象名稱
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
// 遍歷reducers名稱
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
// 若是reducer對應的值是 undefined 輸出警告日誌
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
// 當這個reducer是函數 則加入到finalReducers對象中
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
// 讀取出過濾後的reducers
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
// 開發環境將unexpectedKeyCache設置爲空對象
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
// 檢查各個reducers是否考慮過defualt的狀況,不能返回undefined
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
// combineRducers返回的是一個方法,dispatch最後執行的方法
return function combination(state = {}, action) {
// 若是reducer檢查出有問題就會拋出異常
if (shapeAssertionError) {
throw shapeAssertionError
}
// 開發者環境下
if (process.env.NODE_ENV !== 'production') {
// 對過濾後的reducers和初始化的state進行檢查
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
// 若是有問題就會輸出
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false
const nextState = {}
// 遍歷過濾後的reducers
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
// 根據key取出對應reducer
const reducer = finalReducers[key]
// 根據key將state對應的值取出
const previousStateForKey = state[key]
// 執行咱們reducer的方法,nextStateForKey就是根據actionType返回的state
const nextStateForKey = reducer(previousStateForKey, action)
// 檢查nextStateForKey是不是undefined
if (typeof nextStateForKey === 'undefined') {
// 若是undefined就報錯
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// 將合併後的state賦值到nextState當中
nextState[key] = nextStateForKey
// 若是state的值改變過 則hasChanged置爲true
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 若是改變過 則返回新的state否則則是原有的state({})
// 若是state不傳值就是 空的對象{}
return hasChanged ? nextState : state
}
}
複製代碼
combineReducers
方法會先對傳入的reducers進行校驗,reducer的類型只能是function
,最後返回的是個方法,這個方法很關鍵,由於在disptach時,最後執行的就是這個方法。這個方法有2個參數state
和action
,方法內會根據傳入的action返回state,最後會比較新舊的state,若是不相等,則會返回新的state,若是相等會返回新的state。redux
那麼若是咱們直接對store的state進行操做而不是經過dispatch會發生呢,好比說咱們這樣api
const state = store.getState();
state.name = 'baifann';
複製代碼
咱們看一下combineReducers
中的getUnexpectedStateShapeWarningMessage
這個方法,它會檢查store中初始化的state的key有沒有在各個子reducer當中,若是沒有就會報錯。
/** * @inputState 初始化的state * @reducers 已通過過濾的reducers * @action 隨着combinRecuers傳入的action * @unexpectedKeyCache 開發者環境是一個空的對象,生成環境是undefined */
/** * 檢查合法的reducers是否存在 * * */
function getUnexpectedStateShapeWarningMessage( inputState, reducers, action, unexpectedKeyCache ) {
// 將過濾的reducers的名取出
const reducerKeys = Object.keys(reducers)
const argumentName =
// 若是這個action的type是預製的ActionTypes.INIT
// argumentName就是preloadedState argument passed to createStore
// 否則是previous state received by the reducer
action && action.type === ActionTypes.INIT
? 'preloadedState argument passed to createStore'
: 'previous state received by the reducer'
// 若是過濾後的reducer長度爲0
// 則返回字符串告知沒有一個合法的reducer(reducer必須是function類型)
if (reducerKeys.length === 0) {
return (
'Store does not have a valid reducer. Make sure the argument passed ' +
'to combineReducers is an object whose values are reducers.'
)
}
// 判斷輸入的state是不是obj對象
// 若是不是 則返回字符串告知inputState不合法
if (!isPlainObject(inputState)) {
return (
`The ${argumentName} has unexpected type of "` +
{}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
`". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
)
}
// 傳入的state進行遍歷
// 若是state的對象名不包含在reducer中 而且不包含在unexpectedKeyCache對象中
// unexpectedKeyCache在開發者環境是一個空的對象 所以只要state的對象名不包含在reducer中,這個key就會
// 保存到 unexpectedKeys 當中
const unexpectedKeys = Object.keys(inputState).filter(
key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
)
// 將inputState的key所有設置爲true
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
// 若是這個action的type是定義的定義中的ActionTypes.REPLACE 就返回不執行
if (action && action.type === ActionTypes.REPLACE) return
// 若是unexpectedKeys中有值,則發出警告
if (unexpectedKeys.length > 0) {
return (
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
`Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
)
}
}
複製代碼
compose會返回一個方法,這個方法能夠將傳入的方法依次執行
export default function compose(...funcs) {
// 若是函數方法爲0 則
if (funcs.length === 0) {
// 會將參數直接返回
return arg => arg
}
// 若是隻傳入一個方法則會返回這個方法
if (funcs.length === 1) {
return funcs[0]
}
// a爲上一次回調函數返回的值 b爲當前值
// 效果就是不斷執行數組中的方法最後返回時一個函數
// 這個方法能夠將全部數組中的方法執行
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製代碼
咱們接下來看下createStore.js
這個文件,它只暴露出了createStore的方法,在createStore
中,初始化了一些參數,同時返回了一個store,store中包括了dispatch
,subscribe
,getState
,replaceReducer
,[$$observable]: observable
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore(reducer, preloadedState, enhancer) {
// 若是初始化的state是一個方法而且enhancer也是方法就會報錯
// 若是enhancer是方法若是第4個參數是方法就會報錯
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'
)
}
// 若是初始化的state是方法,enhancer的參數爲undefined
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// enhancer賦值初始話的stae
enhancer = preloadedState
// preloadedState賦值爲undefined
preloadedState = undefined
// 這裏是一個兼容2個參數的處理,當參數僅爲2個 第二個參數爲enhcaner時的處理
}
// 若是enhancer 不是undefined
if (typeof enhancer !== 'undefined') {
// 若是enhancer不是方法會報錯
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 返回enhancer的方法
return enhancer(createStore)(reducer, preloadedState)
}
// 若是reducer不是方法 則報錯
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
// rootReducer賦值到currentReducer當中 實際是一個函數
let currentReducer = reducer
// 當前store中的state 默認是初始化的state
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
// 淺拷貝一個數組 雖然是淺拷貝 可是currentListener不會被nextListener改變
nextListeners = currentListeners.slice()
}
}
function getState() {
// 省略代碼...
}
function subscribe(listener) {
// 省略代碼...
}
function dispatch(action) {
// 省略代碼...
}
function replaceReducer(nextReducer) {
// 省略代碼...
}
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 來初始化store中的state
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
複製代碼
看完以後,咱們可能在這個地方有一點疑惑,就是這裏
// 若是enhancer 不是undefined
if (typeof enhancer !== 'undefined') {
// 若是enhancer不是方法會報錯
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 返回enhancer的方法
return enhancer(createStore)(reducer, preloadedState)
}
複製代碼
這個返回的是什麼呢,咱們知道applyMiddleware
返回的其實就是enhancer,那咱們結合在一塊兒看一下
import compose from './compose'
/** * 建立一個store的加強器,使用中間件來包裝dispath方法,這對於各類任務來講都很方便 * 好比以簡潔的方式進行異步操做,或記錄每一個操做有效負載 * * 查看`redux-thunk`包,這是一箇中間件的例子 * * 由於中間件多是異步的,因此應該是對個enhancer傳參 * * 每個中間件都要提供dispatch和getstate兩個方法做參數 * */
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
// 建立一個store ...args爲reducer, preloadedState
const store = createStore(...args)
// 默認定義disptach方法,是一個拋出的報錯
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
// 中間件的的參數
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 將全部的中間件遍歷,將參數傳入到中間件函數中,返回一箇中間件函數的數組
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// dispatch作了包裝,會在dispatch的同時同時將中間件的方法也返回
dispatch = compose(...chain)(store.dispatch)
// 返回store中的屬性以及新的dispatch方法
return {
...store,
dispatch
}
}
}
複製代碼
若是直接返回了enhancer
那麼返回的其實也是store
,可是這個store
中的dispatch
被包裝過,當dispatch
被執行時,會將全部中間件也依次執行。
接下來分析一下createStore
中的方法
很簡單,就是返回currentState
/** * Reads the state tree managed by the store. * * @returns {any} The current state tree of your application. */
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.'
)
}
// 返回state
return currentState
}
複製代碼
這是將一個回調加入到監聽數組當中,同時,它會返回一個註銷監聽的方法。
function subscribe(listener) {
// listener必須是一個方法
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-reference/store#subscribe(listener) for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
// 把listenr加入到nextListeners的數組當中
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#subscribe(listener) for more details.'
)
}
isSubscribed = false
// 這裏作了個拷貝 作的全部操做不影響currentListener
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
// 在nextListener將它去除
nextListeners.splice(index, 1)
}
}
複製代碼
dispatch首先會檢查參數,隨後會執行currentReducer(currentState, action)
,而這個方法實際就是combineReducers
的
function dispatch(action) {
// 若是dispatch的參數不是action
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// action必須得有type屬性,若是沒有會報錯
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
// 執行reducer 遍歷過濾後的reducer,隨後依次賦值到state當中
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 獲取當前的監聽器
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
// 依次執行監聽器回調
listener()
}
// dispatch默認返回action
return action
}
複製代碼
/** * Replaces the reducer currently used by the store to calculate the state. * * You might need this if your app implements code splitting and you want to * load some of the reducers dynamically. You might also need this if you * implement a hot reloading mechanism for Redux. * * @param {Function} nextReducer The reducer for the store to use instead. * @returns {void} */
/** * 替換reducer * * 動態替換原有的reducer */
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
// 將reducer賦值
currentReducer = nextReducer
// 發送一個dispatch 隨後重置store
dispatch({ type: ActionTypes.REPLACE })
}
複製代碼
這裏不談太多observable
這裏有個使用例子
const state$ = store[Symbol.observable]();
const subscription = state$.subscribe({
next: function(x) {
console.log(x);
}
});
subscription.unsubscribe();
複製代碼
/** * Interoperability point for observable/reactive libraries. * @returns {observable} A minimal observable of state changes. * For more information, see the observable proposal: * https://github.com/tc39/proposal-observable */
function observable() {
const outerSubscribe = subscribe
return {
/** * The minimal observable subscription method. * @param {Object} observer Any object that can be used as an observer. * The observer object should have a `next` method. * @returns {subscription} An object with an `unsubscribe` method that can * be used to unsubscribe the observable from the store, and prevent further * emission of values from the observable. */
/** * @param {Object} 任何對象均可以當作observer * observer應該有一個`next`方法 * @returns {subscription} 一個對象,它有`unsubscribe`方法可以 * 用來從store中unsubscribe observable */
subscribe(observer) {
// observer必須是一個非空的object
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
}
}
}
複製代碼
在講這個方法前,先看下文檔對它的使用說明
TodoActionCreators.js
咱們在文件中建立了2個普通的action。
export function addTodo(text) {
return {
type: 'ADD_TODO',
text
}
}
export function removeTodo(id) {
return {
type: 'REMOVE_TODO',
id
}
}
複製代碼
SomeComponent.js
import { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as TodoActionCreators from './TodoActionCreators'
console.log(TodoActionCreators)
// {
// addTodo: Function,
// removeTodo: Function
// }
class TodoListContainer extends Component {
constructor(props) {
super(props)
const { dispatch } = props
// Here's a good use case for bindActionCreators:
// You want a child component to be completely unaware of Redux.
// We create bound versions of these functions now so we can
// pass them down to our child later.
this.boundActionCreators = bindActionCreators(TodoActionCreators, dispatch)
console.log(this.boundActionCreators)
// {
// addTodo: Function,
// removeTodo: Function
// }
}
componentDidMount() {
// Injected by react-redux:
let { dispatch } = this.props
// Note: this won't work:
// TodoActionCreators.addTodo('Use Redux')
// You're just calling a function that creates an action.
// You must dispatch the action, too!
// This will work:
let action = TodoActionCreators.addTodo('Use Redux')
dispatch(action)
}
render() {
// Injected by react-redux:
let { todos } = this.props
return <TodoList todos={todos} {...this.boundActionCreators} />
// An alternative to bindActionCreators is to pass
// just the dispatch function down, but then your child component
// needs to import action creators and know about them.
// return <TodoList todos={todos} dispatch={dispatch} />
}
}
export default connect(state => ({ todos: state.todos }))(TodoListContainer)
複製代碼
bindActionCreators.js
咱們接下來來看它的源碼
/** * * 在看`bindActionCreator`方法以前能夠先看`bindActionCreators`方法 * * @param {Function} actionCreator 實際就是 action * */
function bindActionCreator(actionCreator, dispatch) {
return function() {
// 返回的是dispath
return dispatch(actionCreator.apply(this, arguments))
}
}
// actionCreators是一個包含衆多actions的對象
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
// actionCreators是函數就表明他是單一的action方法
return bindActionCreator(actionCreators, dispatch)
}
// actionCreator若是不是object 或者它是空的則報錯
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${ actionCreators === null ? 'null' : typeof actionCreators }. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
// 將action的keys遍歷出來
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
// 每一個action的key
const key = keys[i]
// 將action取出 這是一個方法
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
// bindActionCreator返回的是dispatch的返回值
// 實際是action 因此boundActionCreators是一個dispatch function的對象
// 同時若是key相同會被覆蓋
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
複製代碼
使用bindActionCreators
實際能夠建立一個充滿dispatch
方法的對象。而後能夠將這個對象傳遞子組件來使用。
看完源碼後咱們大體瞭解到爲何reducer
必須是function
,store
中的state
爲何會建立和reducer
相應的對象名的state
,爲何只能經過dispatch
來對store
進行操做。另外redux
的一個核心不可變性,redux
自己並不能保證。因此咱們在本身寫的reducer
當中必需要保證不能改變store
原有的對象,必須得從新建立。