注:這篇文章只是講解React Redux這一層,并不包含Redux部分。Redux有計劃去學習,等以后學習了Redux源碼以后再做分析
注:代碼基于現在(2016.12.29)React Redux的最新版本(5.0.1)
這一小節里面先把基礎的Utils代碼過一遍,以后看核心代碼的時候方便一點。由于是Utils不涉及文檔,所以沒有文檔方面的展示
shallowEqual.js從名字中就能看出這個的作用,其實就只是做了一個淺比較,對象中的value直接用 === 來比較,所以如果遇到復雜的object進行比較,就會返回false。最基礎的就是shallowEqual({a:{}}, {a:{}}) === false
const hasOwn = Object.prototype.hasOwnProperty export default function shallowEqual(a, b) { if (a === b) return true let countA = 0 let countB = 0 for (let key in a) { if (hasOwn.call(a, key) && a[key] !== b[key]) return false countA++ } for (let key in b) { if (hasOwn.call(b, key)) countB++ } return countA === countB }
代碼比較簡單,基本思路就是:
比較a對象中的自身屬性是否在b中也存在并且相等,如果不存在或不想等,返回false
比較b中自身屬性的數量是否等于a中自身屬性的數量,如果不相同,返回false
對代碼的一點疑問:
countA++之前,要不要檢查一下是否是OwnProperty?我已經在segmentfault里面提了問題,LionKissDeer在github上發了issue,并得到回復,確實是一個問題。
用countA, countB來記錄數量,而不是用Object.keys(a).length來進行對比,可以理解為減少操作和不必要的內存使用。那么是否只用一個countA,然后再第二個for...in中進行countA--,最后比較countA === 0更好?
storeShape.js這個真的只是store的shape,不需要解釋
import { PropTypes } from "react" export default PropTypes.shape({ subscribe: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired, getState: PropTypes.func.isRequired })warning.js
簡單的一個報錯的方法,主要是檢查了console是否存在的情況。中間有提到一點,如果console打開了"break on all exception"選項,那么就會在這個warning的地方停下
/** * Prints a warning in the console if it exists. * * @param {String} message The warning message. * @returns {void} */ export default function warning(message) { /* eslint-disable no-console */ if (typeof console !== "undefined" && typeof console.error === "function") { console.error(message) } /* eslint-enable no-console */ try { // This error was thrown as a convenience so that if you enable // "break on all exceptions" in your console, // it would pause the execution at this line. throw new Error(message) /* eslint-disable no-empty */ } catch (e) {} /* eslint-enable no-empty */ }verifyPlainObject.js
通過判斷是否是plainObject,使用lodash來判斷,并給個warning。這個方法主要是用在非production環境下,對數據格式進行檢測,并拋出異常
import isPlainObject from "lodash/isPlainObject" import warning from "./warning" export default function verifyPlainObject(value, displayName, methodName) { if (!isPlainObject(value)) { warning( `${methodName}() in ${displayName} must return a plain object. Instead received ${value}.` ) } }wrapActionCreators.js
這里是通過這個方法生成一個(actionCreators)=>(dispatch)=>()=>binded actions的方法。bindActionCreators的作用是返回用dispatch綁定過的actions,具體方法可以在Redux文檔中查看。
然而,我并沒有在react-redux的代碼里看到這個方法的調用,文檔中也沒有提到過這個方法,雖然在mapDispatchToProps.js中看到了一樣的代碼片段…不知道是不是老代碼沒有刪除干凈,還是新功能需要,但是還沒有上線…
import { bindActionCreators } from "redux" export default function wrapActionCreators(actionCreators) { return dispatch => bindActionCreators(actionCreators, dispatch) }Subscription.js
這里是用一個典型的訂閱發布模式,通過對store或父級subscription進行監聽,來進行組件的更新(onStateChange)。
createListenerCollection function先放一個工廠模式的代碼,這段主要是用來生成listener的工廠:
const CLEARED = null const nullListeners = { notify() {} } function createListenerCollection() { // the current/next pattern is copied from redux"s createStore code. // TODO: refactor+expose that code to be reusable here? let current = [] let next = [] return { clear() { next = CLEARED current = CLEARED }, notify() { const listeners = current = next for (let i = 0; i < listeners.length; i++) { listeners[i]() } }, subscribe(listener) { let isSubscribed = true if (next === current) next = current.slice() next.push(listener) return function unsubscribe() { if (!isSubscribed || current === CLEARED) return isSubscribed = false if (next === current) next = current.slice() next.splice(next.indexOf(listener), 1) } } } }
這段很簡單的實現了一個listener的工廠,包含notify, subscribe, unsubscribe和clear等訂閱發布模式的基本功能。
值得注意的是,這里使用了current/next模式,這里主要是為了防止在notify中,listeners[i]()運行的時候對current對象作出內容刪除操作,從而導致notify出錯。
舉個栗子,我們要運行下面這段代碼:
var listener = createListenerCollection(); var helloUnsub = listener.subscribe(()=>{ console.log("Hello"); }); var worldUnsub = listener.subscribe(()=>{ console.log("world"); helloUnsub(); }); var unsub = listener.subscribe(()=>{ console.log("!!"); }); listener.notify(); // 期望輸出的是Hello world !! listener.notify(); // 期望輸出的是world !!
然后我們用修改過沒有用next/current模式的代碼運行:
function createListenerCollection() { let current = [] return { notify() { const listeners = current for (let i = 0; i < listeners.length; i++) { listeners[i]() } }, subscribe(listener) { let isSubscribed = true current.push(listener) return function unsubscribe() { if (!isSubscribed || current === CLEARED) return isSubscribed = false current.splice(current.indexOf(listener), 1) } } } }
看一下輸出結果:
發現第一次輸出的時候,少輸出了一次。
在這里,我們在world的輸出后,取消hello的subscribe。這里就會造成:輸出完world以后,刪除了hello,代碼里listeners.length的判斷就自動減少了1,所以導致!!沒有輸出。
而如果使用next/current模式的話,由于我們對unsubscribe的操作都是對新的next進行操作,所以不會影響listeners,就不會出現上面的問題。
一個簡單的封裝:
export default class Subscription { constructor(store, parentSub) { this.store = store this.parentSub = parentSub this.unsubscribe = null this.listeners = nullListeners } addNestedSub(listener) { this.trySubscribe() return this.listeners.subscribe(listener) } notifyNestedSubs() { this.listeners.notify() } isSubscribed() { return Boolean(this.unsubscribe) } trySubscribe() { if (!this.unsubscribe) { // this.onStateChange is set by connectAdvanced.initSubscription() this.unsubscribe = this.parentSub ? this.parentSub.addNestedSub(this.onStateChange) : this.store.subscribe(this.onStateChange) this.listeners = createListenerCollection() } } tryUnsubscribe() { if (this.unsubscribe) { this.unsubscribe() this.unsubscribe = null this.listeners.clear() this.listeners = nullListeners } } }
唯一需要注意的一點是,他們的onStateChange事件其實是綁定在父級(parentSub)或者store的subscription上面的。至于為什么要綁定在父級或者store上面,是因為父級發生了改變,就會通知下級,下級再通知下下級…所以下級需要連接到上級上。
調用模式大概是這樣子的,由上到下由頂層store一層一層到leaf:
不明白的一點:
這樣子綁定上級的方法,和所有都直接綁定到store上面有什么不同?已提問
shallowEqual的簡單實現,感覺完全可以不用插件自己寫一個,比較簡單。在上面關于shallowEqual的github上面,React Redux的作者有提到建議使用fbjs的shallowEqual
next/current模式,特別適用于在循環中可能會對循環對象進行增刪的情況,可以考慮使用這個模式。通過生成一個影對象,對影對象進行修改,需要循環的時候,再賦值給current對象
一個簡單的訂閱發布模式,多層級的情況下,可以通過監聽上一級來進行從root到leaf的調用
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/86683.html
摘要:另外,內置的函數在經過一系列校驗后,觸發,之后被更改,之后依次調用監聽,完成整個狀態樹的更新??偠灾?,遵守這套規范并不是強制性的,但是項目一旦稍微復雜一些,這樣做的好處就可以充分彰顯出來。 這一篇是接上一篇react進階漫談的第二篇,這一篇主要分析redux的思想和應用,同樣參考了網絡上的大量資料,但代碼同樣都是自己嘗試實踐所得,在這里分享出來,僅供一起學習(上一篇地址:個人博客/s...
摘要:另外,內置的函數在經過一系列校驗后,觸發,之后被更改,之后依次調用監聽,完成整個狀態樹的更新??偠灾?,遵守這套規范并不是強制性的,但是項目一旦稍微復雜一些,這樣做的好處就可以充分彰顯出來。 這一篇是接上一篇react進階漫談的第二篇,這一篇主要分析redux的思想和應用,同樣參考了網絡上的大量資料,但代碼同樣都是自己嘗試實踐所得,在這里分享出來,僅供一起學習(上一篇地址:個人博客/s...
摘要:在幾天前發布了新版本,被合入。但是在版本迭代的背后很多有趣的設計值得了解。參數處理這項改動由提出。對透明化處理中的,達到將包裹起來的目的。對的凍結認為,在中使用和方法是一種反模式。尤其是這樣的新,某些開發者認為將逐漸取代。 showImg(https://segmentfault.com/img/remote/1460000014571148); Redux 在幾天前(2018.04....
摘要:在幾天前發布了新版本,被合入。但是在版本迭代的背后很多有趣的設計值得了解。參數處理這項改動由提出。對透明化處理中的,達到將包裹起來的目的。對的凍結認為,在中使用和方法是一種反模式。尤其是這樣的新,某些開發者認為將逐漸取代。 showImg(https://segmentfault.com/img/remote/1460000014571148); Redux 在幾天前(2018.04....
閱讀 1972·2021-11-23 10:03
閱讀 4130·2021-11-22 09:34
閱讀 2466·2021-10-08 10:05
閱讀 2247·2019-08-30 15:53
閱讀 1686·2019-08-30 13:56
閱讀 1149·2019-08-29 16:52
閱讀 1102·2019-08-26 13:31
閱讀 3346·2019-08-26 11:45