reduxソース読解
8689 ワード
reduxソース読解
まずreduxの公式例からreduxの役割を見る
このように簡単に見ると、reduxは登録購読+データ管理のように感じられ、storeにstateツリーが格納、reducerによってのみ変更可能であるが、reducerの2番目のパラメータはactionであり、ユーザーはdispatch特定actionによってreducerを呼び出してstateを変更する.
公式の例からcreateStoreの重要性がわかりますので、私たちの解読もcreateStoreから始まります.
1. CreateStore
enhancer(拡張機能に相当)の関連コードを一時的に削除し、storeの実行を理解しやすい
コードにはいくつかのポイントがあります.
1.1 currentListenersとNextListenersが存在する理由
NextListenerが存在しない場合、サブスクライバに通知する途中で他のサブスクライバやサブスクライバが発生し、エラーや不確実性を招く
上記のコードのように、dispatchによってstoreが変更され、呼び出しサブスクライバ(listeners)が遍歴されると、1つのlistenerがリスナーキューを変更するため、NextListenersのコピーがなければ、遍歴中の配列にcurrentListenersという直接変更され、遍歴中のindexが信頼できなくなる
これは唯一の方法ではないかもしれません.reduxのissueを調べたところ、お兄さんがずっと前にnextListenersを取り除く方法を提出していたことがわかりました.https://github.com/reduxjs/redux/pull/2376しかし、結局は予測不可能で受け入れられなかったようだ.
そこでNextListenersに参加する必要があり、今回の通知で購読がキャンセルされたかどうかにかかわらず、次の購読にのみ影響し、現在の結果には影響しません.
参考ブログ:Redux:自問自答ですが、例としては自分で書いた方が分かりやすいと思います(x)
2. applyMiddleware
前述したenhancerは、applyMiddlewareがcreateStoreに転送可能なenhancerを返すことを示しています.
この
3. bindActionCreators
このモジュールのソースコードは実際には以下の関数を見るだけでよいが,残りの部分は伝達されたactionCreatorsがオブジェクトであるか関数であるかによってそれぞれこの関数を呼び出すだけである.
ここでactionCreatorは、同じ処理が必要な一連のactionを生成するために使用できるaction:{type:'xxx'}を返します.
例えば入力したパラメータ...argsをpayloadとして対応するactionを生成する combineReducers
この役割は名前のようにreducersを組み合わせることができ、大規模なプロジェクトでは各モジュールが自身のreducerを管理するのに便利である.
このソースコードは比較的に長いので、私たちはまずいくつかの重要ではないエラーを削除して、彼の実装を確認します.
小結
今回reduxのソースコードを読むと、閉パッケージに対する理解がまだ深くないと感じました.コリー化や戻り関数は私を気絶させました==実際のプロジェクトではこのような書き方は使わないはずです(可読性があまり高くないので)、しかし、これも私が他のソースコードを読むレベルを高めました.
まずreduxの公式例からreduxの役割を見る
import { createStore } from 'redux'
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
let store = createStore(counter)
store.subscribe(() => console.log(store.getState()))
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1
このように簡単に見ると、reduxは登録購読+データ管理のように感じられ、storeにstateツリーが格納、reducerによってのみ変更可能であるが、reducerの2番目のパラメータはactionであり、ユーザーはdispatch特定actionによってreducerを呼び出してstateを変更する.
公式の例からcreateStoreの重要性がわかりますので、私たちの解読もcreateStoreから始まります.
1. CreateStore
enhancer(拡張機能に相当)の関連コードを一時的に削除し、storeの実行を理解しやすい
import isPlainObject from 'lodash/isPlainObject'
import $observable from 'symbol-observable'
export const ActionTypes = {
INIT: '@@redux/INIT'
}
export default function createStore(reducer, preloadedState) {
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
// reducer, , action.type state
let currentReducer = reducer
// state,
let currentState = preloadedState
//
let currentListeners = []
// , next /
let nextListeners = currentListeners
//
let isDispatching = false
//
//
//
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
// ,
// , ? action state
function getState() {
return currentState
}
// , listener
//
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
// ,
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?'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
// reducer
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}
// , state , observer next
// prop `[$observable]` , ,
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer) {
if (typeof observer !== 'object') {
throw new TypeError('Expected the observer to be an object.')
}
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$observable]() {
return this
}
}
}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$observable]: observable
}
}
コードにはいくつかのポイントがあります.
1.1 currentListenersとNextListenersが存在する理由
NextListenerが存在しない場合、サブスクライバに通知する途中で他のサブスクライバやサブスクライバが発生し、エラーや不確実性を招く
const store = createStore(reducers.todos)
const unsubscribe1 = store.subscribe(() => {
// listener
const unsubscribe2 = store.subscribe(()=>{})
unsubscribe2()
})
上記のコードのように、dispatchによってstoreが変更され、呼び出しサブスクライバ(listeners)が遍歴されると、1つのlistenerがリスナーキューを変更するため、NextListenersのコピーがなければ、遍歴中の配列にcurrentListenersという直接変更され、遍歴中のindexが信頼できなくなる
これは唯一の方法ではないかもしれません.reduxのissueを調べたところ、お兄さんがずっと前にnextListenersを取り除く方法を提出していたことがわかりました.https://github.com/reduxjs/redux/pull/2376しかし、結局は予測不可能で受け入れられなかったようだ.
そこでNextListenersに参加する必要があり、今回の通知で購読がキャンセルされたかどうかにかかわらず、次の購読にのみ影響し、現在の結果には影響しません.
参考ブログ:Redux:自問自答ですが、例としては自分で書いた方が分かりやすいと思います(x)
2. applyMiddleware
前述したenhancerは、applyMiddlewareがcreateStoreに転送可能なenhancerを返すことを示しています.
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
//
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
//
chain = middlewares.map(middleware => middleware(middlewareAPI))
// compose , ( )
// c chain c3(c2(c1(dispatch)))
// redux compose reduce
dispatch = compose(...chain)(store.dispatch)
// dispatch
return {
...store,
dispatch
}
}
}
この
applyMiddleware
は実は3級のコリー化(簡単な理解では、3回のパラメータを収集してから実行される)関数で、1回目は中間部品で、2回目はcreateStoreで、3回目はcreateStoreのパラメータです.compose
は関数を組み合わせて、使う地方はとても多くて、関数式のプログラミングの思想を体現して、これは余分に見ることができて、言うまでもありません3. bindActionCreators
このモジュールのソースコードは実際には以下の関数を見るだけでよいが,残りの部分は伝達されたactionCreatorsがオブジェクトであるか関数であるかによってそれぞれこの関数を呼び出すだけである.
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
ここでactionCreatorは、同じ処理が必要な一連のactionを生成するために使用できるaction:{type:'xxx'}を返します.
例えば入力したパラメータ...argsをpayloadとして対応するactionを生成する
この役割は名前のようにreducersを組み合わせることができ、大規模なプロジェクトでは各モジュールが自身のreducerを管理するのに便利である.
このソースコードは比較的に長いので、私たちはまずいくつかの重要ではないエラーを削除して、彼の実装を確認します.
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
// reducers function reducer
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
return function combination(state = {}, action) {
let hasChanged = false
const nextState = {}
// reducers , reducer state
// action state , change
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
小結
今回reduxのソースコードを読むと、閉パッケージに対する理解がまだ深くないと感じました.コリー化や戻り関数は私を気絶させました==実際のプロジェクトではこのような書き方は使わないはずです(可読性があまり高くないので)、しかし、これも私が他のソースコードを読むレベルを高めました.