Redux入坑進階之源碼解析
預熱
redux 函數(shù)內部包含了大量柯里化函數(shù)以及代碼組合思想
柯里化函數(shù)(curry)
通俗的來講,可以用一句話概括柯里化函數(shù):返回函數(shù)的函數(shù)
- // example
- const funcA = (a) => {
- return const funcB = (b) => {
- return a + b
- }
- };
上述的funcA函數(shù)接收一個參數(shù),并返回同樣接收一個參數(shù)的funcB函數(shù)。
柯里化函數(shù)有什么好處呢?
- 避免了給一個函數(shù)傳入大量的參數(shù)--我們可以通過柯里化來構建類似上例的函數(shù)嵌套,將參數(shù)的代入分離開,更有利于調試
- 降低耦合度和代碼冗余,便于復用
舉個栗子:
- // 已知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函數(shù),并將listA作為參數(shù)傳入,來構建一個新的函數(shù)。
- // 而新函數(shù)的作用則是:檢查傳入的參數(shù)是否存在于listA里
- const ifDataExist = checkIfDataExist(listA);
- // 使用新函數(shù)來對listB里的每一個元素進行篩選
- const intersectionList = listB.filter(value => ifDataExist(value));
- console.log(intersectionList); // [2, 3, 4]
代碼組合(compose)
代碼組合就像是數(shù)學中的結合律:
- const compose = (f, g) => {
- return (x) => {
- return f(g(x));
- };
- };
- // 還可以再簡潔點
- const compose = (f, g) => (x) => f(g(x));
通過這樣函數(shù)之間的組合,可以大大增加可讀性,效果遠大于嵌套一大堆的函數(shù)調用,并且我們可以隨意更改函數(shù)的調用順序
Redux
combineReducers
- // 回顧一下combineReducers的使用格式
- // 兩個reducer
- const todos = (state = INIT.todos, action) => {
- // ....
- };
- const filterStatus = (state = INIT.filterStatus, action) => {
- // ...
- };
- const appReducer = combineReducers({
- todos,
- filterStatus
- });
還記得combineReducers的黑魔法嗎?即:
- 傳入的Object參數(shù)中,對象的key與value所代表的reducer function同名
- 各個reducer function的名稱和需要傳入該reducer的state參數(shù)同名
源碼標注解讀(省略部分):
- export default function combineReducers(reducers) {
- // ***次篩選,參數(shù)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函數(shù)用于遍歷finalReducers中的reducer,檢查傳入reducer的state是否合法
- assertReducerSanity(finalReducers)
- } catch (e) {
- sanityError = e
- }
- // 返回一個function。該方法接收state和action作為參數(shù)
- return function combination(state = {}, action) {
- // 如果之前的判斷reducers中有不法值,則拋出錯誤
- if (sanityError) {
- throw sanityError
- }
- // 如果不是production環(huán)境則拋出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中進行函數(shù)調用
- for (var i = 0; i < finalReducerKeys.length; i++) {
- var key = finalReducerKeys[i]
- var reducer = finalReducers[key]
- // 這也就是為什么說combineReducers黑魔法--要求傳入的Object參數(shù)中,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)
- // 當***個參數(shù)傳入undefined時,則為各個reducer定義的默認參數(shù)
- var initialState = reducer(undefined, { type: ActionTypes.INIT })
- // ActionTypes.INIT幾乎不會被定義,所以會通過switch的default返回reducer的默認參數(shù)。如果沒有指定默認參數(shù),則返回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必須返回默認的參數(shù)state。這個ActionTypes.INIT就可以用來監(jiān)測當reducer傳入未知type的action時,返回的state是否合法
- export var ActionTypes = {
- INIT: '@@redux/INIT'
- }
- export default function createStore(reducer, initialState, enhancer) {
- // 檢查你的state和enhance參數(shù)有沒有傳反
- 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 // 是否正在分發(fā)事件
- 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中去除掉當前l(fā)istener
- 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判斷調用的狀態(tài)
- 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 作為參數(shù)的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作為參數(shù),兩者組合成為一個新的函數(shù)(enhance),之后在createStore內部,因為enhance的存在,將會變成返回enhancer(createStore)(reducer, initialState)
源碼標注解讀(省略部分):
- // 定義一個代碼組合的方法
- // 傳入一些function作為參數(shù),返回其鏈式調用的形態(tài)。例如,
- // 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為參數(shù)的匿名函數(shù)
- // 這個函數(shù)返回另一個以reducer, initialState, enhancer為參數(shù)的匿名函數(shù)
- 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 作為參數(shù)進行注入,返回一個新的鏈。此時的返回值相當于調用 thunkMiddleware 返回的函數(shù): (next) => (action) => {} ,接收一個next作為其參數(shù)
- chain = middlewares.map(middleware => middleware(middlewareAPI))
- // 并將鏈代入進 compose 組成一個函數(shù)的調用鏈
- // compose(...chain) 返回形如(...args) => f(g(h(...args))),f/g/h都是chain中的函數(shù)對象。
- // 在目前只有 thunkMiddleware 作為 middlewares 參數(shù)的情況下,將返回 (next) => (action) => {}
- // 之后以 store.dispatch 作為參數(shù)進行注入
- 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收到的參數(shù)中有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作為參數(shù),返回形如(createStore) => (reducer, initialState, enhancer) => {}的函數(shù)。
- applyMiddleware(thunkMiddleware)(createStore)
以 createStore 作為參數(shù),調用上一步返回的函數(shù)(reducer, initialState, enhancer) => {}
- applyMiddleware(thunkMiddleware)(createStore)(reducer, initialState)
以(reducer, initialState)為參數(shù)進行調用。
在這個函數(shù)內部,thunkMiddleware被調用,其作用是監(jiān)測type是function的action
因此,如果dispatch的action返回的是一個function,則證明是中間件,則將(dispatch, getState)作為參數(shù)代入其中,進行action 內部下一步的操作。否則的話,認為只是一個普通的action,將通過next(也就是dispatch)進一步分發(fā)。
也就是說,applyMiddleware(thunkMiddleware)作為enhance,最終起了這樣的作用:
對dispatch調用的action(例如,dispatch(addNewTodo(todo)))進行檢查,如果action在***次調用之后返回的是function,則將(dispatch, getState)作為參數(shù)注入到action返回的方法中,否則就正常對action進行分發(fā),這樣一來我們的中間件就完成嘍~
因此,當action內部需要獲取state,或者需要進行異步操作,在操作完成之后進行事件調用分發(fā)的話,我們就可以讓action 返回一個以(dispatch, getState)為參數(shù)的function而不是通常的Object,enhance就會對其進行檢測以便正確的處理。
bindActionCreator
這個方法感覺比較少見,我個人也很少用到
在傳統(tǒng)寫法下,當我們要把 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 傳給它。
- // 在本組件內的應用
- 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的函數(shù)
- 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分發(fā)綁定至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
- }
react-redux
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。
首先獲取傳入的參數(shù),如果沒有則以默認值代替
- 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 作為參數(shù)的函數(shù)。在這個函數(shù)內部,生成了一個叫做Connect的 Component
- // ...
- return function wrapWithConnect(WrappedComponent) {
- const connectDisplayName = `Connect(${getDisplayName(WrappedComponent)})`
- // 檢查參數(shù)合法性
- 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
- // 對數(shù)據(jù)進行監(jiān)聽,發(fā)送改變時調用
- this.setState({ storeState })
- }
- // 取消監(jiān)聽,清除緩存
- 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);