最近研究了Redux源碼,做爲React技術棧中的一份子,保持了其特有的小巧專注。代碼總共不過百來行,其中包含了狀態機,發佈訂閱模式以及高階函數的應用,也確實值得咱們學習借鑑。(如下源碼略做修改和註釋)react
整個應用的state被儲存在一顆object tree中,而且這個object tree只存在於惟一redux
惟一改變state的方法就是觸發action, action是一個已發生事件的普通對象數組
爲了描述action如何改變state tree,就須要reducerpromise
// 判斷是否是純對象,而非構造函數
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
const ActionTypes = {
INIT: '@@redux/INIT'
}
export default ActionTypes;
複製代碼
做爲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
}
}
複製代碼
將多個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;
}
}
複製代碼
將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
}
複製代碼
這一塊靈活應用了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)))
}
複製代碼
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
}
}
}
複製代碼
import createStore from './createStore';
import combineReducers from './combineReducers';
import bindActionCreators from './bindActionCreators';
import applyMiddleware from './applyMiddleware';
export {
createStore,//建立倉庫
combineReducers,//合併reducers
bindActionCreators,//把actionCreator 和 dispatch方法綁定在一塊兒
applyMiddleware
}
複製代碼
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;
複製代碼
// 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)
}
}
複製代碼
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} />
}
}
}
}
複製代碼
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>
)
}
}
複製代碼
import React from 'react';
const ReduxContext = React.createContext(null);
export default ReduxContext;
複製代碼
import Provider from './Provider';
import connect from './connect';
export {
Provider,
connect
}
複製代碼