摘要:粗讀近來沒什么特別要做的事,下班回來的空閑時間也比較多,所以抽空看看懶加載是怎么實現(xiàn)的,特別是看了下的庫的實現(xiàn)。之先別關(guān)注,按他給注釋說測試用。之是組件綁定事件時會觸發(fā)的函數(shù)。
react-lazy-load粗讀
近來沒什么特別要做的事,下班回來的空閑時間也比較多,所以抽空看看懶加載是怎么實現(xiàn)的,特別是看了下 react-lazy-load 的庫的實現(xiàn)。
懶加載這里懶加載場景不是路由分割打包那種,而是單個頁面中有一個很長的列表,列表中的圖片進行懶加載的效果。
在 jquery 時代,這種列表圖片懶加載效果就已經(jīng)有了,那么我們想一想這種在滾動的時候才去加載圖片等資源的方式該如何去實現(xiàn)呢?
大致原理瀏覽器解析 html 的時候,在遇到 img 標簽以及發(fā)現(xiàn) src 屬性的時候,瀏覽器就會去發(fā)請求拿圖片去了。這里就是切入點,根據(jù)這種現(xiàn)象,做下面幾件事:
把列表中所有的圖片的 img 標簽的 src 設(shè)為空
把真實的圖片路徑存成一個 dom 屬性,打個比方:
寫一個檢測列表某一項是否是可見狀態(tài)
全局滾動事件做一個監(jiān)聽,檢測當(dāng)前列表的項是否是可見的,如果可見則給 img 標簽上存著真實圖片路徑賦值給 src 屬性
react-lazy-load知道懶加載的大概原理,來看一下 react-lazy-load 是怎么做的。
大體看了下 react-lazy-load 的實現(xiàn)的總體思路就更加簡單了,本質(zhì)上就是讓需要懶加載的組件包含在這個包提供的 LazyLoad 組件中,不渲染這個組件,然后去監(jiān)聽這個 LazyLoad 組件是否已經(jīng)是可見了,如果是可見了那么就去強制渲染包含在 LazyLoad 組件內(nèi)部需要懶加載的組件了。
這種方式相較于手動去控制 img 標簽來的實在是太方便了,完全以組件為單位,對組件進行懶加載。這樣的話,完全就不需要感知組件內(nèi)部的邏輯和渲染邏輯,無論這個需要懶加載的組件內(nèi)部是有幾個 img 標簽,也完全不用去手動操控 src 屬性的賦值。
react-lazy-load 之 renderclass LazyLoad extends React.Component{ constructor(props) { super(props) this.visible = false } componentDidMount() { // 主要是監(jiān)聽事件 // 省略此處代碼 } shouldComponentUpdate() { return this.visible } componentWillUnmount() { // 主要是移除監(jiān)聽事件 // 省略 } render () { return this.visible ? this.props.children : this.props.placeholder ? this.props.placeholder : } }
從 render 函數(shù)能夠看出來,依據(jù)當(dāng)前 visible 的值來確定是否渲染 this.props.children,如果為 false 則去渲染節(jié)點的占位符。如果外部傳入一個占位節(jié)點,就用這個傳入的占位節(jié)點,否則就用默認的占位符去占位。注意到:shouldComponentUpdate 依據(jù) this.visible 的值去判斷是否更新組件。剩下的,該去看看如何監(jiān)聽事件以及修改 this.visible、強制重新渲染組件的。
react-lazy-load 之 componentDidMountcomponentDidMount() { // It"s unlikely to change delay type on the fly, this is mainly // designed for tests const needResetFinalLazyLoadHandler = (this.props.debounce !== undefined && delayType === "throttle") || (delayType === "debounce" && this.props.debounce === undefined); if (needResetFinalLazyLoadHandler) { off(window, "scroll", finalLazyLoadHandler, passiveEvent); off(window, "resize", finalLazyLoadHandler, passiveEvent); finalLazyLoadHandler = null; } if (!finalLazyLoadHandler) { if (this.props.debounce !== undefined) { finalLazyLoadHandler = debounce(lazyLoadHandler, typeof this.props.debounce === "number" ? this.props.debounce : 300); delayType = "debounce"; } else if (this.props.throttle !== undefined) { finalLazyLoadHandler = throttle(lazyLoadHandler, typeof this.props.throttle === "number" ? this.props.throttle : 300); delayType = "throttle"; } else { finalLazyLoadHandler = lazyLoadHandler; } } if (this.props.overflow) { const parent = scrollParent(ReactDom.findDOMNode(this)); if (parent && typeof parent.getAttribute === "function") { const listenerCount = 1 + (+parent.getAttribute(LISTEN_FLAG)); if (listenerCount === 1) { parent.addEventListener("scroll", finalLazyLoadHandler, passiveEvent); } parent.setAttribute(LISTEN_FLAG, listenerCount); } } else if (listeners.length === 0 || needResetFinalLazyLoadHandler) { const { scroll, resize } = this.props; if (scroll) { on(window, "scroll", finalLazyLoadHandler, passiveEvent); } if (resize) { on(window, "resize", finalLazyLoadHandler, passiveEvent); } } listeners.push(this); checkVisible(this); }
needResetFinalLazyLoadHandler 先別關(guān)注,按他給注釋說測試用。 finalLazyLoadHandler 依據(jù)外部 debounce 和 throttle 來選擇是防抖還是節(jié)流還是都不用。根據(jù)外部傳入的overflow 來確定是否是在某一個節(jié)點中 overflow 的下拉框的懶加載還是普通的整個 window 的懶加載。然后就是依據(jù)是 scroll 還是 resize 來給 window 增加監(jiān)聽事件 finalLazyLoadHandler。 最后就是把這個組件實例放到了 listeners 這個數(shù)組里,然后調(diào)用 checkVisible 檢查是否可見。
react-lazy-load 之 checkVisible/** * Detect if element is visible in viewport, if so, set `visible` state to true. * If `once` prop is provided true, remove component as listener after checkVisible * * @param {React} component React component that respond to scroll and resize */ const checkVisible = function checkVisible(component) { const node = ReactDom.findDOMNode(component); if (!node) { return; } const parent = scrollParent(node); const isOverflow = component.props.overflow && parent !== node.ownerDocument && parent !== document && parent !== document.documentElement; const visible = isOverflow ? checkOverflowVisible(component, parent) : checkNormalVisible(component); if (visible) { // Avoid extra render if previously is visible if (!component.visible) { if (component.props.once) { pending.push(component); } component.visible = true; component.forceUpdate(); } } else if (!(component.props.once && component.visible)) { component.visible = false; if (component.props.unmountIfInvisible) { component.forceUpdate(); } } };
parent 就是找到這個組件的上層組件的 dom 節(jié)點,通過 checkOverflowVisible 和 checkNormalVisible這兩個函數(shù)拿到該節(jié)點是否在可視區(qū)域內(nèi)得到 visible。然后依據(jù) visible的值修改 component 的 visible的值,然后調(diào)用組件的 forceUpdate 方法,強制讓組件重新渲染。主要到組件的 visible 并不是掛載到 state 上,所以這里不是用 setState 來重新渲染。
react-lazy-load 之 checkNormalVisible/** * Check if `component` is visible in document * @param {node} component React component * @return {bool} */ const checkNormalVisible = function checkNormalVisible(component) { const node = ReactDom.findDOMNode(component); // If this element is hidden by css rules somehow, it"s definitely invisible if (!(node.offsetWidth || node.offsetHeight || node.getClientRects().length)) return false; let top; let elementHeight; try { ({ top, height: elementHeight } = node.getBoundingClientRect()); } catch (e) { ({ top, height: elementHeight } = defaultBoundingClientRect); } const windowInnerHeight = window.innerHeight || document.documentElement.clientHeight; const offsets = Array.isArray(component.props.offset) ? component.props.offset : [component.props.offset, component.props.offset]; // Be compatible with previous API return (top - offsets[0] <= windowInnerHeight) && (top + elementHeight + offsets[1] >= 0); };
主要邏輯就是拿到組件的 dom 節(jié)點的 getBoundingClientRect 返回值和 window.innerHeight 進行比較來判斷是否是在可視范圍內(nèi)。這里在比較的時候還有個 component.props.offset 也參與了比較,說明設(shè)置了 offset 的時候,組件快要出現(xiàn)在可視范圍的時候就會去重新渲染組件而不是出現(xiàn)在可視范圍內(nèi)才去重新渲染。
react-lazy-load 之 lazyLoadHandlerlazyLoadHandler 是組件綁定事件時會觸發(fā)的函數(shù)。
const lazyLoadHandler = () => { for (let i = 0; i < listeners.length; ++i) { const listener = listeners[i]; checkVisible(listener); } // Remove `once` component in listeners purgePending(); };
每次監(jiān)聽事件執(zhí)行的時候,都去檢查一下組件,如果滿足條件就去強制渲染組件。
react-lazy-load 之 componentWillUnmountcomponentWillUnmount() { if (this.props.overflow) { const parent = scrollParent(ReactDom.findDOMNode(this)); if (parent && typeof parent.getAttribute === "function") { const listenerCount = (+parent.getAttribute(LISTEN_FLAG)) - 1; if (listenerCount === 0) { parent.removeEventListener("scroll", finalLazyLoadHandler, passiveEvent); parent.removeAttribute(LISTEN_FLAG); } else { parent.setAttribute(LISTEN_FLAG, listenerCount); } } } const index = listeners.indexOf(this); if (index !== -1) { listeners.splice(index, 1); } if (listeners.length === 0) { off(window, "resize", finalLazyLoadHandler, passiveEvent); off(window, "scroll", finalLazyLoadHandler, passiveEvent); } }
組件卸載的時候,把一些綁定事件解綁一下,細節(jié)也不說了。
總結(jié)拋開 react-lazy-load 一些實現(xiàn)細節(jié),從總體把握整個懶加載的過程,其實懶加載的原理并不難。當(dāng)時我也看了一下 vue 那邊的 vue-lazyLoad 這個庫想寫一個對比的文章,我以為這個 vue 庫的內(nèi)容會寫的和 react-lazy-load 差不多,結(jié)果發(fā)現(xiàn) vue-lazyLoad 代碼很長而且好像比較復(fù)雜,所以也就沒看了。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/108247.html
摘要:這個大概是的鉤子吧在每一次插入操作的時候都將節(jié)點這類型方法可以看出來是在調(diào)用對應(yīng)的方法因為開始的時候就導(dǎo)入進來了插入節(jié)點操作的時候都需要加入子節(jié)點有子元素也就是的時候遞歸調(diào)用循環(huán)子節(jié)點生成對應(yīng)著一些操作之后都要觸發(fā)鉤子函數(shù)。 snabbdom 本文的snabbdom源碼分析采用的是0.54版本(即未用ts重寫前的最后一版) 前期了解 snabbdom被用作vue的虛擬dom。本文的一個...
摘要:讀了周勇老師的從零開始寫框架,感覺干貨還是挺多的。不過,這本書中的從零開始并不是指的零基礎(chǔ),而是從無到有。還是先說說目前的感受吧。第五章講了的優(yōu)化文件上傳和下載集成安全框架和框架。如果大家看了這本書有什么新的感悟,也歡迎分享給我。 讀了周勇老師的《從零開始寫javaweb框架》,感覺干貨還是挺多的。想把自己的收獲分享給大家。不過,這本書中的從零開始并不是指的零基礎(chǔ),而是從無到有。所以,...
閱讀 1725·2021-10-18 13:34
閱讀 3911·2021-09-08 10:42
閱讀 1557·2021-09-02 09:56
閱讀 1611·2019-08-30 15:54
閱讀 3133·2019-08-29 18:44
閱讀 3304·2019-08-26 18:37
閱讀 2218·2019-08-26 12:13
閱讀 458·2019-08-26 10:20