成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

react-lazy-load粗讀

dailybird / 2226人閱讀

摘要:粗讀近來(lái)沒(méi)什么特別要做的事,下班回來(lái)的空閑時(shí)間也比較多,所以抽空看看懶加載是怎么實(shí)現(xiàn)的,特別是看了下的庫(kù)的實(shí)現(xiàn)。之先別關(guān)注,按他給注釋說(shuō)測(cè)試用。之是組件綁定事件時(shí)會(huì)觸發(fā)的函數(shù)。

react-lazy-load粗讀

近來(lái)沒(méi)什么特別要做的事,下班回來(lái)的空閑時(shí)間也比較多,所以抽空看看懶加載是怎么實(shí)現(xiàn)的,特別是看了下 react-lazy-load 的庫(kù)的實(shí)現(xiàn)。

懶加載

這里懶加載場(chǎng)景不是路由分割打包那種,而是單個(gè)頁(yè)面中有一個(gè)很長(zhǎng)的列表,列表中的圖片進(jìn)行懶加載的效果。

jquery 時(shí)代,這種列表圖片懶加載效果就已經(jīng)有了,那么我們想一想這種在滾動(dòng)的時(shí)候才去加載圖片等資源的方式該如何去實(shí)現(xiàn)呢?

大致原理

瀏覽器解析 html 的時(shí)候,在遇到 img 標(biāo)簽以及發(fā)現(xiàn) src 屬性的時(shí)候,瀏覽器就會(huì)去發(fā)請(qǐng)求拿圖片去了。這里就是切入點(diǎn),根據(jù)這種現(xiàn)象,做下面幾件事:

把列表中所有的圖片的 img 標(biāo)簽的 src 設(shè)為空

把真實(shí)的圖片路徑存成一個(gè) dom 屬性,打個(gè)比方:

寫一個(gè)檢測(cè)列表某一項(xiàng)是否是可見(jiàn)狀態(tài)

全局滾動(dòng)事件做一個(gè)監(jiān)聽(tīng),檢測(cè)當(dāng)前列表的項(xiàng)是否是可見(jiàn)的,如果可見(jiàn)則給 img 標(biāo)簽上存著真實(shí)圖片路徑賦值給 src 屬性

react-lazy-load

知道懶加載的大概原理,來(lái)看一下 react-lazy-load 是怎么做的。

大體看了下 react-lazy-load 的實(shí)現(xiàn)的總體思路就更加簡(jiǎn)單了,本質(zhì)上就是讓需要懶加載的組件包含在這個(gè)包提供的 LazyLoad 組件中,不渲染這個(gè)組件,然后去監(jiān)聽(tīng)這個(gè) LazyLoad 組件是否已經(jīng)是可見(jiàn)了,如果是可見(jiàn)了那么就去強(qiáng)制渲染包含在 LazyLoad 組件內(nèi)部需要懶加載的組件了。

這種方式相較于手動(dòng)去控制 img 標(biāo)簽來(lái)的實(shí)在是太方便了,完全以組件為單位,對(duì)組件進(jìn)行懶加載。這樣的話,完全就不需要感知組件內(nèi)部的邏輯和渲染邏輯,無(wú)論這個(gè)需要懶加載的組件內(nèi)部是有幾個(gè) img 標(biāo)簽,也完全不用去手動(dòng)操控 src 屬性的賦值。

react-lazy-load 之 render
class LazyLoad extends React.Component{
    constructor(props) {
        super(props)
        this.visible = false
    }
    componentDidMount() {
        // 主要是監(jiān)聽(tīng)事件
        // 省略此處代碼
    }
    shouldComponentUpdate() {
        return this.visible
    }
    componentWillUnmount() {
        // 主要是移除監(jiān)聽(tīng)事件
        // 省略
    }
    render () {
        return this.visible
                ? this.props.children
                : this.props.placeholder
                    ? this.props.placeholder
                    : 
} }

render 函數(shù)能夠看出來(lái),依據(jù)當(dāng)前 visible 的值來(lái)確定是否渲染 this.props.children,如果為 false 則去渲染節(jié)點(diǎn)的占位符。如果外部傳入一個(gè)占位節(jié)點(diǎn),就用這個(gè)傳入的占位節(jié)點(diǎn),否則就用默認(rèn)的占位符去占位。注意到:shouldComponentUpdate 依據(jù) this.visible 的值去判斷是否更新組件。剩下的,該去看看如何監(jiān)聽(tīng)事件以及修改 this.visible、強(qiáng)制重新渲染組件的。

react-lazy-load 之 componentDidMount
  componentDidMount() {
    // 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)注,按他給注釋說(shuō)測(cè)試用。 finalLazyLoadHandler 依據(jù)外部 debouncethrottle 來(lái)選擇是防抖還是節(jié)流還是都不用。根據(jù)外部傳入的overflow 來(lái)確定是否是在某一個(gè)節(jié)點(diǎn)中 overflow 的下拉框的懶加載還是普通的整個(gè) window 的懶加載。然后就是依據(jù)是 scroll 還是 resize 來(lái)給 window 增加監(jiān)聽(tīng)事件 finalLazyLoadHandler。 最后就是把這個(gè)組件實(shí)例放到了 listeners 這個(gè)數(shù)組里,然后調(diào)用 checkVisible 檢查是否可見(jiàn)。

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 就是找到這個(gè)組件的上層組件的 dom 節(jié)點(diǎn),通過(guò) checkOverflowVisiblecheckNormalVisible這兩個(gè)函數(shù)拿到該節(jié)點(diǎn)是否在可視區(qū)域內(nèi)得到 visible。然后依據(jù) visible的值修改 componentvisible的值,然后調(diào)用組件的 forceUpdate 方法,強(qiáng)制讓組件重新渲染。主要到組件的 visible 并不是掛載到 state 上,所以這里不是用 setState 來(lái)重新渲染。

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é)點(diǎn)的 getBoundingClientRect 返回值和 window.innerHeight 進(jìn)行比較來(lái)判斷是否是在可視范圍內(nèi)。這里在比較的時(shí)候還有個(gè) component.props.offset 也參與了比較,說(shuō)明設(shè)置了 offset 的時(shí)候,組件快要出現(xiàn)在可視范圍的時(shí)候就會(huì)去重新渲染組件而不是出現(xiàn)在可視范圍內(nèi)才去重新渲染。

react-lazy-load 之 lazyLoadHandler

lazyLoadHandler 是組件綁定事件時(shí)會(huì)觸發(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)聽(tīng)事件執(zhí)行的時(shí)候,都去檢查一下組件,如果滿足條件就去強(qiáng)制渲染組件。

react-lazy-load 之 componentWillUnmount
 componentWillUnmount() {
    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);
    }
  }

組件卸載的時(shí)候,把一些綁定事件解綁一下,細(xì)節(jié)也不說(shuō)了。

總結(jié)

拋開(kāi) react-lazy-load 一些實(shí)現(xiàn)細(xì)節(jié),從總體把握整個(gè)懶加載的過(guò)程,其實(shí)懶加載的原理并不難。當(dāng)時(shí)我也看了一下 vue 那邊的 vue-lazyLoad 這個(gè)庫(kù)想寫一個(gè)對(duì)比的文章,我以為這個(gè) vue 庫(kù)的內(nèi)容會(huì)寫的和 react-lazy-load 差不多,結(jié)果發(fā)現(xiàn) vue-lazyLoad 代碼很長(zhǎng)而且好像比較復(fù)雜,所以也就沒(méi)看了。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/108247.html

相關(guān)文章

  • snabbdom源碼粗讀

    摘要:這個(gè)大概是的鉤子吧在每一次插入操作的時(shí)候都將節(jié)點(diǎn)這類型方法可以看出來(lái)是在調(diào)用對(duì)應(yīng)的方法因?yàn)殚_(kāi)始的時(shí)候就導(dǎo)入進(jìn)來(lái)了插入節(jié)點(diǎn)操作的時(shí)候都需要加入子節(jié)點(diǎn)有子元素也就是的時(shí)候遞歸調(diào)用循環(huán)子節(jié)點(diǎn)生成對(duì)應(yīng)著一些操作之后都要觸發(fā)鉤子函數(shù)。 snabbdom 本文的snabbdom源碼分析采用的是0.54版本(即未用ts重寫前的最后一版) 前期了解 snabbdom被用作vue的虛擬dom。本文的一個(gè)...

    svtter 評(píng)論0 收藏0
  • 從零開(kāi)始的WEB框架——感悟

    摘要:讀了周勇老師的從零開(kāi)始寫框架,感覺(jué)干貨還是挺多的。不過(guò),這本書中的從零開(kāi)始并不是指的零基礎(chǔ),而是從無(wú)到有。還是先說(shuō)說(shuō)目前的感受吧。第五章講了的優(yōu)化文件上傳和下載集成安全框架和框架。如果大家看了這本書有什么新的感悟,也歡迎分享給我。 讀了周勇老師的《從零開(kāi)始寫javaweb框架》,感覺(jué)干貨還是挺多的。想把自己的收獲分享給大家。不過(guò),這本書中的從零開(kāi)始并不是指的零基礎(chǔ),而是從無(wú)到有。所以,...

    MRZYD 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<