原文連接:https://github.com/ecmadao/Co...
轉載請註明出處javascript本文不涉及redux的使用方法,所以可能更適合使用過redux的玩家翻閱?html
curry
)通俗的來說,能夠用一句話歸納柯里化函數:返回函數的函數react
// example const funcA = (a) => { return const funcB = (b) => { return a + b } };
上述的funcA
函數接收一個參數,並返回一樣接收一個參數的funcB
函數。git
柯里化函數有什麼好處呢?github
避免了給一個函數傳入大量的參數--咱們能夠經過柯里化來構建相似上例的函數嵌套,將參數的代入分離開,更有利於調試redux
下降耦合度和代碼冗餘,便於複用緩存
舉個栗子:app
// 已知listA, listB兩個Array,都由int組成,須要篩選出兩個Array的交集 const listA = [1, 2, 3, 4, 5]; const listB = [2, 3, 4]; const checkIfDataExist = (list) => { return (target) => { return list.some(value => value === target) }; }; // 調用一次checkIfDataExist函數,並將listA做爲參數傳入,來構建一個新的函數。 // 而新函數的做用則是:檢查傳入的參數是否存在於listA裏 const ifDataExist = checkIfDataExist(listA); // 使用新函數來對listB裏的每個元素進行篩選 const intersectionList = listB.filter(value => ifDataExist(value)); console.log(intersectionList); // [2, 3, 4]
compose
)代碼組合就像是數學中的結合律:less
const compose = (f, g) => { return (x) => { return f(g(x)); }; }; // 還能夠再簡潔點 const compose = (f, g) => (x) => f(g(x));
經過這樣函數之間的組合,能夠大大增長可讀性,效果遠大於嵌套一大堆的函數調用,而且咱們能夠隨意更改函數的調用順序
combineReducers
// 回顧一下combineReducers的使用格式 // 兩個reducer const todos = (state = INIT.todos, action) => { // .... }; const filterStatus = (state = INIT.filterStatus, action) => { // ... }; const appReducer = combineReducers({ todos, filterStatus });
還記得
combineReducers
的黑魔法嗎?即:
傳入的Object參數中,對象的
key
與value
所表明的reducer function
同名各個
reducer function
的名稱和須要傳入該reducer的state
參數同名
源碼標註解讀(省略部分):
export default function combineReducers(reducers) { // 第一次篩選,參數reducers爲Object // 篩選掉reducers中不是function的鍵值對 var reducerKeys = Object.keys(reducers); var finalReducers = {} for (var i = 0; i < reducerKeys.length; i++) { var key = reducerKeys[i]; if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } var finalReducerKeys = Object.keys(finalReducers) // 二次篩選,判斷reducer中傳入的值是否合法(!== undefined) // 獲取篩選完以後的全部key var sanityError try { // assertReducerSanity函數用於遍歷finalReducers中的reducer,檢查傳入reducer的state是否合法 assertReducerSanity(finalReducers) } catch (e) { sanityError = e } // 返回一個function。該方法接收state和action做爲參數 return function combination(state = {}, action) { // 若是以前的判斷reducers中有不法值,則拋出錯誤 if (sanityError) { throw sanityError } // 若是不是production環境則拋出warning if (process.env.NODE_ENV !== 'production') { var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action) if (warningMessage) { warning(warningMessage) } } var hasChanged = false var nextState = {} // 遍歷全部的key和reducer,分別將reducer對應的key所表明的state,代入到reducer中進行函數調用 for (var i = 0; i < finalReducerKeys.length; i++) { var key = finalReducerKeys[i] var reducer = finalReducers[key] // 這也就是爲何說combineReducers黑魔法--要求傳入的Object參數中,reducer function的名稱和要和state同名的緣由 var previousStateForKey = state[key] var nextStateForKey = reducer(previousStateForKey, action) // 若是reducer返回undefined則拋出錯誤 if (typeof nextStateForKey === 'undefined') { var errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } // 將reducer返回的值填入nextState nextState[key] = nextStateForKey // 若是任一state有更新則hasChanged爲true hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } } // 檢查傳入reducer的state是否合法 function assertReducerSanity(reducers) { Object.keys(reducers).forEach(key => { var reducer = reducers[key] // 遍歷所有reducer,並給它傳入(undefined, action) // 當第一個參數傳入undefined時,則爲各個reducer定義的默認參數 var initialState = reducer(undefined, { type: ActionTypes.INIT }) // ActionTypes.INIT幾乎不會被定義,因此會經過switch的default返回reducer的默認參數。若是沒有指定默認參數,則返回undefined,拋出錯誤 if (typeof initialState === 'undefined') { throw new Error( `Reducer "${key}" returned undefined during initialization. ` + `If the state passed to the reducer is undefined, you must ` + `explicitly return the initial state. The initial state may ` + `not be undefined.` ) } var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.') if (typeof reducer(undefined, { type }) === 'undefined') { throw new Error( `Reducer "${key}" returned undefined when probed with a random type. ` + `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` + `namespace. They are considered private. Instead, you must return the ` + `current state for any unknown actions, unless it is undefined, ` + `in which case you must return the initial state, regardless of the ` + `action type. The initial state may not be undefined.` ) } }) }
createStore
// 回顧下使用方法 const store = createStore(reducers, state, enhance);
源碼標註解讀(省略部分):
// 對於未知的action.type,reducer必須返回默認的參數state。這個ActionTypes.INIT就能夠用來監測當reducer傳入未知type的action時,返回的state是否合法 export var ActionTypes = { INIT: '@@redux/INIT' } export default function createStore(reducer, initialState, enhancer) { // 檢查你的state和enhance參數有沒有傳反 if (typeof initialState === 'function' && typeof enhancer === 'undefined') { enhancer = initialState initialState = undefined } // 若是有傳入合法的enhance,則經過enhancer再調用一次createStore if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)(reducer, initialState) } if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') } var currentReducer = reducer var currentState = initialState var currentListeners = [] var nextListeners = currentListeners var isDispatching = false // 是否正在分發事件 function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } // 咱們在action middleware中常用的getState()方法,返回當前state function getState() { return currentState } // 註冊listener,同時返回一個取消事件註冊的方法。當調用store.dispatch的時候調用listener function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected listener to be a function.') } var isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false // 從nextListeners中去除掉當前listener ensureCanMutateNextListeners() var index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } } // dispatch方法接收的action是個對象,而不是方法。 // 這個對象實際上就是咱們自定義action的返回值,由於dispatch的時候,已經調用過咱們的自定義action了,好比 dispatch(addTodo()) 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?' ) } // 調用dispatch的時候只能一個個調用,經過dispatch判斷調用的狀態 if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } // 遍歷調用各個linster var listeners = currentListeners = nextListeners for (var i = 0; i < listeners.length; i++) { listeners[i]() } return action } // Replaces the reducer currently used by the store to calculate the state. function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer dispatch({ type: ActionTypes.INIT }) } // 當create store的時候,reducer會接受一個type爲ActionTypes.INIT的action,使reducer返回他們默認的state,這樣能夠快速的造成默認的state的結構 dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer } }
thunkMiddleware
源碼及其簡單簡直給跪...
// 返回以 dispatch 和 getState 做爲參數的action export default function thunkMiddleware({ dispatch, getState }) { return next => action => { if (typeof action === 'function') { return action(dispatch, getState); } return next(action); }; }
applyMiddleware
先複習下用法:
// usage import {createStore, applyMiddleware} from 'redux'; import thunkMiddleware from 'redux-thunk'; const store = createStore( reducers, state, applyMiddleware(thunkMiddleware) );
applyMiddleware
首先接收thunkMiddleware
做爲參數,二者組合成爲一個新的函數(enhance
),以後在createStore
內部,由於enhance
的存在,將會變成返回enhancer(createStore)(reducer, initialState)
源碼標註解讀(省略部分):
// 定義一個代碼組合的方法 // 傳入一些function做爲參數,返回其鏈式調用的形態。例如, // compose(f, g, h) 最終返回 (...args) => f(g(h(...args))) export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } else { const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) } } export default function applyMiddleware(...middlewares) { // 最終返回一個以createStore爲參數的匿名函數 // 這個函數返回另外一個以reducer, initialState, enhancer爲參數的匿名函數 return (createStore) => (reducer, initialState, enhancer) => { var store = createStore(reducer, initialState, enhancer) var dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } // 每一個 middleware 都以 middlewareAPI 做爲參數進行注入,返回一個新的鏈。此時的返回值至關於調用 thunkMiddleware 返回的函數: (next) => (action) => {} ,接收一個next做爲其參數 chain = middlewares.map(middleware => middleware(middlewareAPI)) // 並將鏈代入進 compose 組成一個函數的調用鏈 // compose(...chain) 返回形如(...args) => f(g(h(...args))),f/g/h都是chain中的函數對象。 // 在目前只有 thunkMiddleware 做爲 middlewares 參數的狀況下,將返回 (next) => (action) => {} // 以後以 store.dispatch 做爲參數進行注入 dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
一臉懵逼?不要緊,來結合實際使用總結一下:
當咱們搭配redux-thunk
這個庫的時候,在redux
配合components
時,一般這麼寫
import thunkMiddleware from 'redux-thunk'; import { createStore, applyMiddleware, combineReducer } from 'redux'; import * as reducers from './reducers.js'; const appReducer = combineReducer(reducers); const store = createStore(appReducer, initialState, applyMiddleware(thunkMiddleware));
還記得當createStore
收到的參數中有enhance
時會怎麼作嗎?
// createStore.js if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)(reducer, initialState) }
也就是說,會變成下面的狀況
applyMiddleware(thunkMiddleware)(createStore)(reducer, initialState)
applyMiddleware(thunkMiddleware)
applyMiddleware
接收thunkMiddleware
做爲參數,返回形如(createStore) => (reducer, initialState, enhancer) => {}
的函數。
applyMiddleware(thunkMiddleware)(createStore)
以 createStore 做爲參數,調用上一步返回的函數(reducer, initialState, enhancer) => {}
applyMiddleware(thunkMiddleware)(createStore)(reducer, initialState)
以(reducer, initialState)爲參數進行調用。
在這個函數內部,thunkMiddleware
被調用,其做用是監測type
是function
的action
所以,若是dispatch
的action
返回的是一個function
,則證實是中間件,則將(dispatch, getState)
做爲參數代入其中,進行action
內部下一步的操做。不然的話,認爲只是一個普通的action
,將經過next
(也就是dispatch
)進一步分發。
也就是說,applyMiddleware(thunkMiddleware)
做爲enhance
,最終起了這樣的做用:
對dispatch
調用的action
(例如,dispatch(addNewTodo(todo)))
進行檢查,若是action
在第一次調用以後返回的是function
,則將(dispatch, getState)
做爲參數注入到action
返回的方法中,不然就正常對action
進行分發,這樣一來咱們的中間件就完成嘍~
所以,當action
內部須要獲取state
,或者須要進行異步操做,在操做完成以後進行事件調用分發的話,咱們就可讓action
返回一個以(dispatch, getState)
爲參數的function
而不是一般的Object
,enhance
就會對其進行檢測以便正確的處理。
bindActionCreator
這個方法感受比較少見,我我的也不多用到
在傳統寫法下,當咱們要把 state 和 action 注入到子組件中時,通常會這麼作:
import { connect } from 'react-redux'; import {addTodo, deleteTodo} from './action.js'; class TodoComponect extends Component { render() { return ( <ChildComponent deleteTodo={this.props.deleteTodo} addTodo={this.props.addTodo} /> ) } } function mapStateToProps(state) { return { state } } function mapDispatchToProps(dispatch) { return { deleteTodo: (id) => { dispatch(deleteTodo(id)); }, addTodo: (todo) => { dispatch(addTodo(todo)); } } } export default connect(mapStateToProps, mapDispatchToProps)(TodoComponect);
使用bindActionCreators
能夠把 action 轉爲同名 key 的對象,但使用 dispatch 把每一個 action 包圍起來調用
唯一使用 bindActionCreators 的場景是當你須要把 action creator 往下傳到一個組件上,卻不想讓這個組件覺察到 Redux 的存在,並且不但願把 Redux store 或 dispatch 傳給它。
import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import {addTodo, deleteTodo} as TodoActions from './action.js'; class TodoComponect extends React.Component { // 在本組件內的應用 addTodo(todo) { let action = TodoActions.addTodo(todo); this.props.dispatch(action); } deleteTodo(id) { let action = TodoActions.deleteTodo(id); this.props.dispatch(action); } render() { let dispatch = this.props.dispatch; // 傳遞給子組件 let boundActionCreators = bindActionCreators(TodoActions, dispatch); return ( <ChildComponent {...boundActionCreators} /> ) } } function mapStateToProps(state) { return { state } } export default connect(mapStateToProps)(TodoComponect)
bindActionCreator
源碼解析function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)) } // bindActionCreators期待一個Object做爲actionCreators傳入,裏面是 key: action export default function bindActionCreators(actionCreators, dispatch) { // 若是隻是傳入一個action,則經過bindActionCreator返回被綁定到dispatch的函數 if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } 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"?` ) } // 遍歷並經過bindActionCreator分發綁定至dispatch var keys = Object.keys(actionCreators) var boundActionCreators = {} for (var i = 0; i < keys.length; i++) { var key = keys[i] var actionCreator = actionCreators[key] if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators }
Provider
export default class Provider extends Component { getChildContext() { // 將其聲明爲 context 的屬性之一 return { store: this.store } } constructor(props, context) { super(props, context) // 接收 redux 的 store 做爲 props this.store = props.store } render() { return Children.only(this.props.children) } } if (process.env.NODE_ENV !== 'production') { Provider.prototype.componentWillReceiveProps = function (nextProps) { const { store } = this const { store: nextStore } = nextProps if (store !== nextStore) { warnAboutReceivingStore() } } } Provider.propTypes = { store: storeShape.isRequired, children: PropTypes.element.isRequired } Provider.childContextTypes = { store: storeShape.isRequired }
connect
傳入mapStateToProps
,mapDispatchToProps
,mergeProps
,options
。
首先獲取傳入的參數,若是沒有則以默認值代替
const defaultMapStateToProps = state => ({}) // eslint-disable-line no-unused-vars const defaultMapDispatchToProps = dispatch => ({ dispatch }) const { pure = true, withRef = false } = options
以後,經過
const finalMergeProps = mergeProps || defaultMergeProps
選擇合併stateProps
,dispatchProps
,parentProps
的方式,默認的合併方式 defaultMergeProps
爲:
const defaultMergeProps = (stateProps, dispatchProps, parentProps) => ({ ...parentProps, ...stateProps, ...dispatchProps })
返回一個以 Component 做爲參數的函數。在這個函數內部,生成了一個叫作Connect
的 Component
// ... return function wrapWithConnect(WrappedComponent) { const connectDisplayName = `Connect(${getDisplayName(WrappedComponent)})` // 檢查參數合法性 function checkStateShape(props, methodName) {} // 合併props function computeMergedProps(stateProps, dispatchProps, parentProps) { const mergedProps = finalMergeProps(stateProps, dispatchProps, parentProps) if (process.env.NODE_ENV !== 'production') { checkStateShape(mergedProps, 'mergeProps') } return mergedProps } // start of Connect class Connect extends Component { constructor(props, context) { super(props, context); this.store = props.store || context.store const storeState = this.store.getState() this.state = { storeState } this.clearCache() } computeStateProps(store, props) { // 調用configureFinalMapState,使用傳入的mapStateToProps方法(或默認方法),將state map進props } configureFinalMapState(store, props) {} computeDispatchProps(store, props) { // 調用configureFinalMapDispatch,使用傳入的mapDispatchToProps方法(或默認方法),將action使用dispatch封裝map進props } configureFinalMapDispatch(store, props) {} // 判斷是否更新props updateStatePropsIfNeeded() {} updateDispatchPropsIfNeeded() {} updateMergedPropsIfNeeded() {} componentDidMount() { // 內部調用this.store.subscribe(this.handleChange.bind(this)) this.trySubscribe() } handleChange() { const storeState = this.store.getState() const prevStoreState = this.state.storeState // 對數據進行監聽,發送改變時調用 this.setState({ storeState }) } // 取消監聽,清除緩存 componentWillUnmount() { this.tryUnsubscribe() this.clearCache() } render() { this.renderedElement = createElement(WrappedComponent, this.mergedProps ) return this.renderedElement } } // end of Connect Connect.displayName = connectDisplayName Connect.WrappedComponent = WrappedComponent Connect.contextTypes = { store: storeShape } Connect.propTypes = { store: storeShape } return hoistStatics(Connect, WrappedComponent) } // ...
咱們看見,在connect的最後,返回了使用hoistStatics
包裝的Connect
和WrappedComponent
hoistStatics是什麼鬼?爲何使用它?
Copies non-react specific statics from a child component to a parent component. Similar to Object.assign, but with React static keywords blacklisted from being overridden.
也就是說,它相似於Object.assign
,做用是將子組件中的 static 方法複製進父組件,但不會覆蓋組件中的關鍵字方法(如 componentDidMount)
import hoistNonReactStatic from 'hoist-non-react-statics'; hoistNonReactStatic(targetComponent, sourceComponent);