對於 Redux 結構與代碼實現的剖析,以及項目中的高級用法,不進行對於API的介紹react
recdcer,initState,enhancer
enhancer
是一個高階函數,用於加強create出來的store,他的參數是createStore
,返回一個更強大的store生成函數。(功能相似於middleware)。storeCreator
其實就能夠當作是一個enhancer,在createStore的時候將saga揉入了進去只不過不是做爲createStore的第三個參數完成,而是使用middleware
完成。function createStore(reducer, preloadedState, enhancer) {
if (typeof enhancer !== 'undefined') {
// createStore 做爲enhancer的參數,返回一個被增強的createStore,而後再將reducer, preloadedState傳進去生成store
return enhancer(createStore)(reducer, preloadedState);
}
// ......
return {
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
};
}
複製代碼
function applyMiddleware() {
// 將傳入的中間件放入middlewares
for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
// return了一個 enhancer函數,參數爲createStore,內部對store進行了加強
return function (createStore) {
return function () {
// 將createStore的參數傳入createStore,並生成store
var store = createStore.apply(undefined, args);
// 加強 dispatch
var _dispatch = compose.apply(undefined, chain)(store.dispatch);
// return 一個被加強了dispatch的store
return _extends({}, store, {
dispatch: _dispatch
});
};
};
}
複製代碼
store有四個基礎方法: dispatch、subscribe、getState、replaceReducerredux
function dispatch(action) {
// 校驗 action 格式是否合法
if (typeof action.type === 'undefined') {
throw new Error('action 必須有type屬性');
}
// 不能夠在 reducer 進行中發起 dispatch
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
// 標記 dispatch 狀態
isDispatching = true;
// 執行相應的 reducer 並獲取新更新的 state
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
// 把上次subscribe時獲得的新的監聽函數列表,賦值成爲當前的監聽函數列表
var listeners = currentListeners = nextListeners;
// dispatch 的時候會依次執行 nextListeners 的監聽函數
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
return action;
}
複製代碼
function subscribe(listener) {
// 若是是在 dispatch時註冊subscribe,拋出警告
if (isDispatching) {
throw new Error('......');
}
// 將監聽函數放入一個隊列
nextListeners.push(listener);
// return 一個函數,用於註銷監聽事件
return function unsubscribe() {
// 一樣的,不能再 dispatch 時進行註銷操做
if (isDispatching) {
throw new Error('......');
}
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
};
}
複製代碼
function getState() {
if (isDispatching) {
throw new Error('不容許在reducer執行中獲取state');
}
// retuen 上次 dispatch 時所更新的 currentState
return currentState;
}
複製代碼
function replaceReducer(nextReducer) {
// 檢驗新的 reducer 是不是一個函數
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.');
}
// 替換掉當前的 reducer
currentReducer = nextReducer;
// 發起一次新的 action, 這樣可使 sisteners 函數列表執行一遍,也能夠更新一遍 currentState
dispatch({ type: ActionTypes.REPLACE });
}
複製代碼
用於將多個reducer組合成一個reducer,接受一個對象,對象的每一個屬性便是單個reducer,各個reducer的key須要和傳入該reducer的state參數同名。數組
function combineReducers(reducers) {
// 全部傳入 reducers 的 key
var reducerKeys = Object.keys(reducers);
var finalReducers = {};
// 遍歷reducerKeys,將合法的 reducers 放入 finalReducers
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
// 可用的 reducers的 key
var finalReducerKeys = Object.keys(finalReducers);
var unexpectedKeyCache = void 0;
{
unexpectedKeyCache = {};
}
var shapeAssertionError = void 0;
// 將每一個 reducer 都執行一遍,檢驗返回的 state 是否有爲undefined的狀況
try {
assertReducerShape(finalReducers);
} catch (e) {
shapeAssertionError = e;
}
// return 一個組合過的 reducer 函數,返回值爲 state 是否有變化
return function combination() {
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var action = arguments[1];
// 若是有返回的state不合法的reducer,拋出錯誤
if (shapeAssertionError) {
throw shapeAssertionError;
}
{
// 校驗 state 與 finalReducers 的合法性
var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
if (warningMessage) {
warning(warningMessage);
}
}
var hasChanged = false;
var nextState = {};
// 遍歷全部可用的reducer,將reducer的key所對應的state,代入到reducer中調用
for (var _i = 0; _i < finalReducerKeys.length; _i++) {
var _key = finalReducerKeys[_i];
var reducer = finalReducers[_key];
// reducer key 所對應的 state,這也是爲何 reducer 名字要與 state 名字相對應的緣由
var previousStateForKey = state[_key];
// 調用 reducer
var nextStateForKey = reducer(previousStateForKey, action);
// reducer 返回了新的 state,調用store.getState時返回的就是他
nextState[_key] = nextStateForKey;
// 新舊 state 是否有變化 ?
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
return hasChanged ? nextState : state;
};
}
複製代碼
其實就是改變action發起的方式,以前是dispatch的方式,用bindActionCreators將actionCreator包裝後,生成一個key爲actionType,value爲接受 payload 的函數的對象,發起action的時候直接調用這裏面名爲跟action的type同名的函數app
function bindActionCreator(actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(this, arguments));
};
}
複製代碼
function bindActionCreators(actionCreators, dispatch) {
// 若是傳入一個函數,說明只有一個,actionCreator,返回一個能夠進行 dispatch 的函數
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch);
}
if ((typeof actionCreators === 'undefined' ? 'undefined' : _typeof(actionCreators)) !== 'object' || actionCreators === null) {
throw new Error('校驗actionCreators是不是對象');
}
// 檢索出 actionCreators 的 key
var keys = Object.keys(actionCreators);
var boundActionCreators = {};
// 循環將 actionCreators 中的項用 bindActionCreator 包裝一遍,放入 boundActionCreators 對象中並return
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;
}
複製代碼
將多個函數組合成一個,從右往左依次執行異步
function compose() {
// 獲取傳入參數的映射
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
funcs[_key] = arguments[_key];
}
// 若是參數爲0,return 一個 所傳即所得的函數
if (funcs.length === 0) {
return function (arg) {
return arg;
};
}
// 若是隻有一個,返回此參數
if (funcs.length === 1) {
return funcs[0];
}
// 使用 reduce 將全部傳入的函數組合爲一個函數,每一次執行reduce,a做爲前一個函數都會被這個return的函數從新賦值
return funcs.reduce(function (a, b) {
// 每次執行 reduce 都會返回這個函數,這個函數裏返回的前一個函數接受下一個函數的返回值做爲參數
return function () {
return a(b.apply(undefined, arguments));
};
});
}
複製代碼
其實applyMiddleware就是將傳入的中間件進行組合,生成了一個接受 createStore爲參數的函數(enhancer)。ide
// applyMiddleware將傳入的中間件組合成一個enhancer
// 而後再傳入createStore改形成一個加強版的createStore
// 最後傳入reducer 和 initialState 生成 store。
const store = applyMiddleware(...middlewares)(createStore)(reducer, initialState);
// 其實跟這樣寫沒什麼區別
const store = createStore(reducer, initialState, applyMiddleware(...middlewares));
複製代碼
function applyMiddleware() {
// 將傳入的中間件組合成一個數組
for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
// 返回一個接受 createStore 爲參數的函數,也就是 enhancer
return function (createStore) {
// 其實這就是最終返回的被加強的 createStore
return function () {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
// 生成一個 store
var store = createStore.apply(undefined, args);
// 聲明一個_dispatch,用於替換 store 的 dispatch
var _dispatch = function dispatch() {
throw new Error('不容許在構建中間件時進行調度');
};
// 返回一個middlewareAPI,下一步將會被帶入中間件,使得每個中間件中都會有 getState 與 dispatch (例如redux-thunk)
// 這裏面的 dispatch中,將會執行_dispatch(被加強的dispatch)
var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch() {
return _dispatch.apply(undefined, arguments);
}
};
// 每個中間件都執行一遍 middlewareAPI
var chain = middlewares.map(function (middleware) {
return middleware(middlewareAPI);
});
// 將 chain 用compose進行組合,因此傳入的中間件依賴必須是倒序的
// 並傳入 store.dispatch,生成一個被加強的 dispatch
_dispatch = compose.apply(undefined, chain)(store.dispatch);
// 生成 store, 使用 _dispatch 替換 store 原始的 dispatch
return _extends({}, store, {
dispatch: _dispatch
});
};
};
}
複製代碼
redux-thunk
感覺一下applyMiddlewareredux-thunk 可使dispatch接受一個函數,以便於進行異步操做函數
import { createStore, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';
import reducer from './reducers';
const store = createStore(
reducer,
{},
applyMiddleware(reduxThunk),
);
複製代碼
function createThunkMiddleware(extraArgument) {
// reuturn 一個接受dispatch, getState的函數,
// 這個函數返回的函數又接受上一個中間件的返回值,也就是被上一個中間件包裝過的dispatch
// 若是接受的action是個函數,那麼就將dispatch, getState傳進去
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
export default thunk;
複製代碼
function applyMiddleware() {
...
var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch() {
return _dispatch.apply(undefined, arguments);
}
};
var chain = middlewares.map(function () {
// 這是middleware將middlewareAPI傳進去後return的函數
return function(next) {
return function(action) {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
}
}
});
// 將store.dispatch,也就是next傳進去
_dispatch = compose.apply(undefined, chain)(store.dispatch);
}
複製代碼
用於綁定react 與 redux,其主要提供了兩個功能源碼分析
用於包裝組件樹,將store傳入context中,使其子節點均可以拿到store,不須要一級一級的往下傳。性能
class Provider extends Component {
// 將 store 放入 context 中
getChildContext() {
return { store: this.store}
}
constructor(props, context) {
super(props, context)
this.store = props.store;
}
render() {
return Children.only(this.props.children)
}
}
複製代碼
connect 用於state與容器組件之間的綁定。
connect 接受三個參數mapStateToProps, mapDispatchToProps, mergeProps
用於定義須要傳入容器組件的state與dispatch,而後return一個接受容器組件的函數(高階組件),這個高階函數會對將組合好的props混入進容器組件。ui
var containerComponent = connect(mapStateToProps,mapDispatchToProps)(someComponent);
ReactDOM.render(
<Provider store={store}> <HashRouter> <div> <Route exact path="/" component={containerComponent} /> </div> </HashRouter> </Provider>, document.querySelector('.doc') ); 複製代碼
const mapStateToProps = (state) => {
return {
stateName: state[stateName],
};
}
複製代碼
// mapDispatchToProps 是個函數
const mapDispatchToProps = (dispatch) => {
return {
dispatchName: (action) => {
dispatch(action);
},
};
}
// or 當 mapDispatchToProps 是個對象時源碼中的處理
export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
return (mapDispatchToProps && typeof mapDispatchToProps === 'object')
? wrapMapToPropsConstant(dispatch => bindActionCreators(mapDispatchToProps, dispatch))
: undefined
}
複製代碼
// 默認是將 mapStateToProps, mapDispatchToProps 與組件自身的props進行 merge
const mergeProps = (stateProps, dispatchProps, ownProps) => {
return { ...ownProps, ...stateProps, ...dispatchProps };
}
複製代碼
finalPropsSelectorFactory(dispatch, {...options})
return 一個
pureFinalPropsSelector
函數,這個函數接受兩個參數,(state, props)並返回一個 mergeProps, 他將會在高階組件wrapWithConnect
中使用並傳入store.getState()和props,並以此對比當前的 props 以決定在shouldComponentUpdate
時是否須要更新
connectAdvanced(finalPropsSelectorFactory)
return 一個接受 容器組件爲參數的高階組件(wrapWithConnect)。 wrapWithConnect須要的變量與屬性,這也就是
connect
最終 return 的結果。
wrapWithConnect(WrappedComponent)
這個就是被
connectAdvanced
返回的高階組件,其接受一個容器組件做爲參數,在內部建立一個Connect
組件並在 render 的時候將整合好的 props 傳入 容器組件。
hoistStatics(a, b) 將 b 的屬性複製到 a
用於在包裝組件的時候,將傳入的容器組件內的屬性都複製到 Connect 組件
function hoistNonReactStatics(targetComponent, sourceComponent, blacklist) {
// 若是傳入的 b 是字符串,直接return a
if (typeof sourceComponent !== 'string') {
// 層層遞歸,直到拿到 sourceComponent 的構造函數
var inheritedComponent = Object.getPrototypeOf(sourceComponent);
if (inheritedComponent && inheritedComponent !== Object.getPrototypeOf(Object)) {
hoistNonReactStatics(targetComponent, inheritedComponent, blacklist);
}
// b 的全部自有屬性的 key ,包括 Symbols 屬性
var keys = Object.getOwnPropertyNames(sourceComponent);
keys = keys.concat(Object.getOwnPropertySymbols(sourceComponent));
// 過濾掉某些屬性,並將 b 的屬性複製給 a
for (var i = 0; i < keys.length; ++i) {
var key = keys[i];
if (!REACT_STATICS[key] && !KNOWN_STATICS[key] && (!blacklist || !blacklist[key])) {
var descriptor = Object.getOwnPropertyDescriptor(sourceComponent, key);
Object.defineProperty(targetComponent, key, descriptor);
}
}
// return 一個被添加了 b 的屬性的 a
return targetComponent;
}
return targetComponent;
}
複製代碼
function connect( mapStateToProps, mapDispatchToProps, mergeProps){
// 對傳入的參數進行類型校驗 與 封裝
const initMapStateToProps = match(mapStateToProps, defaultMapStateToPropsFactories, 'mapStateToProps')
const initMapDispatchToProps = match(mapDispatchToProps, defaultMapDispatchToPropsFactories, 'mapDispatchToProps')
const initMergeProps = match(mergeProps, defaultMergePropsFactories, 'mergeProps')
// return 一個接受 容器組件 爲參數的高階組件(wrapWithConnect)
return connectAdvanced(finalPropsSelectorFactory)
}
// 接受的實際上是 `finalPropsSelectorFactory`
function connectAdvanced(selectorFactory) {
const storeKey = 'store';
// 用於說明訂閱對象
const subscriptionKey = storeKey + 'Subscription';
// 定義 contextTypes 與 childContextTypes 用於返回的高階函數裏的包裝組件 Connect
const contextTypes = {
[storeKey]: storeShape,
[subscriptionKey]: subscriptionShape,
}
const childContextTypes = {
[subscriptionKey]: subscriptionShape,
}
// 返回一個高階組件
return function wrapWithConnect(WrappedComponent) {
// 這是一個接受真假 與 提示語 並拋出錯誤的方法,這裏用來校驗傳入的是不是個函數
invariant(typeof WrappedComponent == 'function', `You must pass a component to the function`)
// 將要傳入 finalPropsSelectorFactory 的 option
const selectorFactoryOptions = {
getDisplayName: name => `ConnectAdvanced(${name})`,
methodName: 'connectAdvanced',
renderCountProp: undefined,
shouldHandleStateChanges: true,
storeKey: 'store',
withRef: false,
displayName: getDisplayName(WrappedComponent.name),
wrappedComponentName: WrappedComponent.displayName || WrappedComponent.name,
WrappedComponent
}
// 用於生成一個 selector,用於Connect組件內部的更新控制
function makeSelectorStateful(sourceSelector, store) {
// wrap the selector in an object that tracks its results between runs.
const selector = {
// 比較 state 與 當前的selector的props,並更新selector
// selector 有三個屬性:
// shouldComponentUpdate: 是否容許組件更新更新
// props: 將要更新的props
// error: catch 中的錯誤
run: function runComponentSelector(props) {
try {
const nextProps = sourceSelector(store.getState(), props)
if (nextProps !== selector.props || selector.error) {
selector.shouldComponentUpdate = true
selector.props = nextProps
selector.error = null
}
} catch (error) {
selector.shouldComponentUpdate = true
selector.error = error
}
}
}
return selector
}
// 最終 return 的組件,用於包裝傳入的WrappedComponent
class Connect extends Component {
constructor(props, context) {
super(props, context)
this.store = props['store'] || context['store']
this.propsMode = Boolean(props['store'])
// 校驗是否傳入了 store
invariant(this.store, `Could not find store in either the context') this.initSelector() this.initSubscription() } componentDidMount() { // 會把 onStateChange 掛載到對store的訂閱裏 // 內部調用了 store.subscribe(this.onStateChange) this.subscription.trySubscribe() // 更新一遍 props this.selector.run(this.props) if (this.selector.shouldComponentUpdate) this.forceUpdate() } // 每次更新 props 都去對比一遍 props componentWillReceiveProps(nextProps) { this.selector.run(nextProps) } // 根據 selector 來進行組件更新的控制 shouldComponentUpdate() { return this.selector.shouldComponentUpdate } // 初始化 selector,用於組件props更新的控制 initSelector() { // 用於比較state與props的函數。並返回 merge 後的props const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions) this.selector = makeSelectorStateful(sourceSelector, this.store) this.selector.run(this.props) } // 初始化訂閱模型: this.subscription initSubscription() { // 定義須要訂閱的數據源,並將其傳入 Subscription 生成一個 subscription 對象 const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey] this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this)) this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription) } // 數據的監聽函數 onStateChange() { this.selector.run(this.props) } // 將 selector.props 傳入到傳入的組件 render() { const selector = this.selector selector.shouldComponentUpdate = false return createElement(WrappedComponent, selector.props) } } // 上面定義的 type 將做爲 Connect 組件的屬性 Connect.WrappedComponent = WrappedComponent Connect.displayName = displayName Connect.childContextTypes = childContextTypes Connect.contextTypes = contextTypes Connect.propTypes = contextTypes // 將傳入的組件的屬性複製進父組件 return hoistStatics(Connect, WrappedComponent) } } 複製代碼
場景:
方案:
利用
redux.combineReducers
與store.replaceReducer
組合與更新reducer
// 初始化 store 的時候,將 reducer 記錄下來
// initReducer: 初始化時的 reducer 對象
var reducers = combineReducers(initReducer);
const store = createStore(
reducers,
initState
);
store.reducers = initReducer;
// 加載子組件的時候,動態將新的 reducer 注入
function assignReducer(reducer) {
// 合併新老 reducer
const newReducer = Object.assign(store.reducers, reducer);
// 經 combineReducers 組合後進行替換
store.replaceReducer(combineReducers(newReducer));
}
複製代碼
場景:
方案:
利用 store.subscribe, 監聽 dispatch 時記錄下此時的 狀態
const stateTimeline = [ initState ]; // 記錄狀態的時間線
let stateIndex = 0; // 當前所處狀態的索引
// 當時間節點發生改變的時候,更替 state
const reducer = (state, action) => {
switch (action.type) {
case 'CHANGE_STATE_INDEX':
const currentState = action.playload.currentState;
return currentState;
default:
return state;
}
};
const saveState = () => {
// 將當前狀態push進時間線
stateTimeline.push(store.getState);
stateIndex++;
};
// 註冊監聽事件
store.subscribe(saveState);
// 獲取某個時間節點的 state
const getSomeNodeState = () => {
return stateTimeline[stateIndex];
};
// 時間線控制器
const timeNodeChangeHandle = (someIndex) => {
stateIndex = someIndex;
store.dispatch({
type: 'CHANGE_STATE_INDEX',
playload: {
currentState: getSomeNodeState();
}
});
};
複製代碼