Redux源碼解析

最近研究了Redux源碼,做爲React技術棧中的一份子,保持了其特有的小巧專注。代碼總共不過百來行,其中包含了狀態機,發佈訂閱模式以及高階函數的應用,也確實值得咱們學習借鑑。(如下源碼略做修改和註釋)react

一、三大原則

單一數據源

整個應用的state被儲存在一顆object tree中,而且這個object tree只存在於惟一redux

State是隻讀的

惟一改變state的方法就是觸發action, action是一個已發生事件的普通對象數組

使用純函數來執行修改

爲了描述action如何改變state tree,就須要reducerpromise

源碼目錄

util/isPlainObject

// 判斷是否是純對象,而非構造函數
export default function isPlainObject(obj) {
    if(typeof obj != 'object' || obj === null) {
        return false;
    }
    let proto = obj;
    // 取到obj最終_proto_指向
    while(Object.getPrototypeOf(proto)) {
        proto = Object.getPrototypeOf(proto)
    }
    return Object.getPrototypeOf(obj) === proto;
}
複製代碼

此純對象的判斷,判斷對象最初的_proto_指向和_proto_最終指向的判斷是否爲同一個來判斷bash

util/actionTypes.js

const ActionTypes = {
    INIT: '@@redux/INIT'
}
export default ActionTypes;
複製代碼

createStore.js

做爲redux中的C位,其中的方法包括getState, dispatch, subscribe方法app

export default function createStore(reducer, preloadedState) {
    if(typeof reducer != 'function') {
        throw new Error('reducer不是函數')
    }
    let currentReducer = reducer; // 狀態處理器
    let currentState = preloadedState; // 初始狀態
    let currentListeners = []; // 監聽函數隊列
    function getState() {
        return currentState;
    }

    function dispatch(action) {
        if(!isPlainObject(action)) {
            throw new Error('action不是純對象')
        }
        if(typeof action.type == 'undefined') {
            throw new Error('action的type未定義')
        }
        // 獲取當前state
        currentState = createReducer(currentState, action);
        // 發佈訂閱模式
        for(let i=0; i<currentListeners.length; i++) {
            const listener = currentListeners[i];
            listener();
        }
        return action;
    }

    // 訂閱
    function subscribe(listener) {
        let subscribed = true;
        currentListeners.push(listener);
        // 取消訂閱
        return function unsubscribe() {
            // 重複訂閱優化
            if(!subscribed) {
                return ;
            }
            const index = currentListeners.indexOf(listener);
            currentListeners.splice(index, 1);
            subscirbed = false;
        }
    }

    // 建立store初始化值
    dispatch({type:ActionTypes.INIT});

    return {
        getState,// 返回狀態
        dispatch,// 派發動做
        subscirbe
    }
}
複製代碼

combineReducers.js

將多個reducer合併到一個對象中,方便維護ide

export default function(reducers) {
    // 返回reducers名稱的數組
    const reducerKeys = Object.keys(reducers);
    return function (state = {}, action) {
        const nextState = {}; 
        for(let i = 0; i < reducerKeys.length; i++) {
            const key = reducerKeys[i];
            const reducer = reducers[key];
            const previousStateForKey = state[key]; // 老狀態
            const nextStateForKey = reducer(previousStateForKey, action); // 新狀態
            nextState[key] = nextStateForKey;
        }
        return nextState;
    }
}
複製代碼

bindActionCreators.js

將dispatch整合到action中函數

function bindActionCreator(actionCreator, dispatch) {
    return function() {
        return dispatch(actionCreator.apply(this, arguments))
    }
}
/**
 * 
 * @param {Function|Object} actionCreators 
 * @param {Function} dispatch  對redux Store生效的dispatch方法
 * @returns {Function|Object} 若是返回對象的話相似與原始對象,可是每一個對象元素添加了dispatch方法
 */
export default function bindActionCreators(actionCreators, dispatch) {
    if(typeof actionCreators === 'function') {
        return bindActionCreator(actionCreators, dispatch)
    }
    if(typeof actionCreators !== 'object' || actionCreators === null) {
        throw new Error(
            `bindActionCreators應該是一個對象或者函數類型, 而不是接收到${
                actionCreators === null ? 'null' : typeof actionCreators
              }. ` +
                `是否是這樣引用 "import ActionCreators from" 而不是 "import * as ActionCreators from"?`
        )
    }

    // actionCreators爲對象的時候
    const boundActionCreators = {}
    for (const key in actionCreators) {
        const actionCreator = actionCreators[key]
        if (typeof actionCreator === 'function') {
            boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
        }
    }
    return boundActionCreators
}
複製代碼

compose.js

這一塊靈活應用了reduce方法和高階函數,實現參數函數從右向左執行學習

/**
 * 
 * @param  {...any} funcs 
 * @returns 包含從右向左執行的函數參數。好比,compose(f, g, h) 等同於 (...args) => f(g(h(...args)))
 */
export default function compose(...funcs) {
    if(funcs.length === 0) {
        return arg => arg
    }
    if(funcs.length === 1) {
        return funcs[0]
    }

    return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製代碼

applyMiddleware.js

import compose from './compose'

export default function applyMiddleware(...middlewares) {
    return createStore => (...args) => {
        const store = createStore(...args)
        let dispatch = () => {
            throw new Error('構造中間鍵的時候不能運行dispatch,不然其餘中間鍵將接收不到dispatch')
        }
        const middlewareAPI = {
            getState: store.getState,
            dispatch: (...args) => dispatch(...args)
        }
    
        const chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)
    
        return {
            ...store,
            dispatch
        }
    }
}
複製代碼

index.js

import createStore from './createStore';
import combineReducers from './combineReducers';
import bindActionCreators from './bindActionCreators';
import applyMiddleware from './applyMiddleware';
export {
    createStore,//建立倉庫
    combineReducers,//合併reducers
    bindActionCreators,//把actionCreator 和 dispatch方法綁定在一塊兒
    applyMiddleware
}
複製代碼

中間鍵實踐

redux-thunk中間鍵實現

function createThunkMiddleware(extraArgument) {
    return ({dispatch, getState}) => next => action => {
        if(typeof action === 'function') {
            return action(dispatch, getState, extraArgument)
        } else {
            next(action)
        }
    }
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
複製代碼

redux-promise

// Promise判斷:有then方法的函數對象
function isPromise(obj) {
    return !obj&&(typeof obj == 'object' || typeof obj == 'function') && (typeof obj.then == 'function')
}

export default function({dispatch, getState}) {
    return next => action => {
        return isPromise(action.payload)? action.payload.then(result => {
            dispatch({...action, payload: result})
        }).catch((error) => {
            dispatch({...action, payload:error, error: true});
            return Promise.reject(error)
        }) : next(action)
    }
}
複製代碼

React-Redux實現

connect.js

import React, {Component} from 'react';
import { bindActionCreators } from '../redux';
import ReduxContext from './context';

export default function(mapStateToProps, mapDispatchToProps) {
    return function(WrappedComponent) {
        return class extends Component {
            static contextType = ReduxContext;
            constructor(props, context) {
                super(props);
                this.state = mapStateToProps(context.store.getState());
            }
            componentDidMount() {
                this.unsubscribe = this.context.store.subscribe(() => {
                    this.setState(mapStateToProps(this.context.store.getState()))
                })
            }
            componentWillMount() {
                this.unsubscribe()
            }
            render() {
                let actions = {}
                if(typeof mapDispatchToProps == 'function') {
                    actions = mapDispatchToProps(this.context.store.dispatch)
                } else {
                    actions = bindActionCreators(mapDispatchToProps, this.context.store.dispatch);
                }

                return <WrappedComponent dispatch={this.context.store.dispatch} {...this.state} {...actions} />
            }
        }
    }
}
複製代碼

Provider.js

import React, { Component } from 'react';
import ReduxContext from './context';
export default class Provider extends Component {
    render() {
        return (
            <ReduxContext.Provider value ={{store: this.props.state}}>
                {this.props.children}
            </ReduxContext.Provider>
        )
    }
}
複製代碼

context.js

import React from 'react';
const ReduxContext = React.createContext(null);
export default ReduxContext;
複製代碼

index.js

import Provider from './Provider';
import connect from './connect';
export {
    Provider,
    connect
}
複製代碼
相關文章
相關標籤/搜索