[源码浏览]经由过程react-infinite-scroller明白转动加载要点

react-infinite-scroller就是一个组件,重要逻辑就是addEventListener绑定scroll事宜。

看它的源码重要意义不在晓得怎样运用它,而是晓得今后处置惩罚转动加载要注重的东西。

此处跳到
总结

初识

参数:

// 衬着出来的DOM元素name
element: 'div',
// 是不是能继承转动衬着
hasMore: false,
// 是不是在定阅事宜的时刻实行事宜
initialLoad: true,
// 示意当前翻页的值(每衬着一次递增)
pageStart: 0,
// 通报ref,返回此组件衬着的 DOM
ref: null,
// 触发衬着的间隔
threshold: 250,
// 是不是在window上绑定和处置惩罚间隔
useWindow: true,
// 是不是反向转动,即到顶端后衬着
isReverse: false,
// 是不是运用捕捉形式
useCapture: false,
// 衬着前的loading组件
loader: null,
// 自定义转动组件的父元素
getScrollParent: null,

深切

componentDidMount

componentDidMount() {
  this.pageLoaded = this.props.pageStart;
  this.attachScrollListener();
}

实行attachScrollListener

attachScrollListener

attachScrollListener() {
  const parentElement = this.getParentElement(this.scrollComponent);
  
  if (!this.props.hasMore || !parentElement) {
    return;
  }

  let scrollEl = window;
  if (this.props.useWindow === false) {
    scrollEl = parentElement;
  }
  scrollEl.addEventListener(
    'mousewheel',
    this.mousewheelListener,
    this.props.useCapture,
  );
  scrollEl.addEventListener(
    'scroll',
    this.scrollListener,
    this.props.useCapture,
  );
  scrollEl.addEventListener(
    'resize',
    this.scrollListener,
    this.props.useCapture,
  );
  
  if (this.props.initialLoad) {
    this.scrollListener();
  }
}

此处经由过程getParentElement猎取父组件(用户自定义父组件或许当前dom的parentNode)

然后绑定了3个事宜,分别是scroll,resize,mousewheel

前2种都绑定scrollListenermousewheel是一个非标准事宜,是不发起在临盆形式中运用的。

那末这里为何要运用呢?

mousewheel处理chrome的守候bug

此处的mousewheel事宜是为了处置惩罚chrome浏览器的一个特征(不晓得是不是是一种bug)。

stackoverflow:Chrome的转动守候题目

上面这个题目重要形貌,当在运用滚轮加载,而且加载会触发ajax要求的时刻,当滚轮抵达底部,会涌现一个冗长而且无任何行动的守候(长达2-3s)。

window.addEventListener("mousewheel", (e) => {
    if (e.deltaY === 1) {
        e.preventDefault()
    }
})

以上绑定能够消弭这个”bug”。

个人并没有遇到过这类状况,不晓得是不是有遇到过能够说说处理方案。

getParentElement

getParentElement(el) {
  const scrollParent =
    this.props.getScrollParent && this.props.getScrollParent();
  if (scrollParent != null) {
    return scrollParent;
  }
  return el && el.parentNode;
}

上面用到了getParentElement,很好明白,运用用户自定义的父组件,或许当前组件DOM.parentNode

scrollListener

scrollListener() {
  const el = this.scrollComponent;
  const scrollEl = window;
  const parentNode = this.getParentElement(el);

  let offset;
  // 运用window的状况
  if (this.props.useWindow) {
    const doc = document.documentElement || document.body.parentNode || document.body;
    const scrollTop = scrollEl.pageYOffset !== undefined
        ? scrollEl.pageYOffset
        : doc.scrollTop;
    // isReverse指 转动到顶端,load新组件
    if (this.props.isReverse) {
      // 相反形式猎取到顶端间隔
      offset = scrollTop;
    } else {
      // 一般形式则猎取到底端间隔
      offset = this.calculateOffset(el, scrollTop);
    }
    // 不运用window的状况
  } else if (this.props.isReverse) {
    // 相反形式组件到顶端的间隔
    offset = parentNode.scrollTop;
  } else {
    // 一般形式组件到底端的间隔
    offset = el.scrollHeight - parentNode.scrollTop - parentNode.clientHeight;
  }

  // 此处应该要推断确保转动组件一般显现
  if (
    offset < Number(this.props.threshold) &&
    (el && el.offsetParent !== null)
  ) {
    // 卸载事宜
    this.detachScrollListener();
    // 卸载事宜后再实行 loadMore
    if (typeof this.props.loadMore === 'function') {
      this.props.loadMore((this.pageLoaded += 1));
    }
  }
}

组件中心。

几个进修/温习点

  1. offsetParent

    offsetParent返回一个指向近来的包括该元素的定位元素.

    offsetParent很有效,由于盘算offsetTopoffsetLeft都是相对于offsetParent边境的。

    ele.offsetParent为 null 的3种状况:

    • ele 为body
    • ele 的positionfixed
    • ele 的displaynone

      此组件中offsetParent处置惩罚了2种状况

      1. useWindow的状况下(即事宜绑定在window,转行动用在body)

        经由过程递归猎取offsetParent抵达顶端的高度(offsetTop)。

        calculateTopPosition(el) {
         if (!el) {
           return 0;   
         }
         return el.offsetTop + this.calculateTopPosition(el.offsetParent);   
        }
      2. 经由过程推断offsetParent不为null的状况,确保转动组件一般显现

          if (
            offset < Number(this.props.threshold) &&
            (el && el.offsetParent !== null)
          ) {/* ... */ }
  2. scrollHeightclientHeight

    在无转动的状况下,scrollHeightclientHeight相称,都为height+padding*2

    在有转动的状况下,scrollHeight示意现实内容高度,clientHeight示意视口高度。

  3. 每次实行loadMore前卸载事宜。

    确保不会反复(过量)实行loadMore,由于先卸载事宜再实行loadMore,能够确保在实行过程当中,scroll事宜是无效的,然后再每次componentDidUpdate的时刻从新绑定事宜。

render

render() {
  // 猎取porps
  const renderProps = this.filterProps(this.props);
  const {
    children,
    element,
    hasMore,
    initialLoad,
    isReverse,
    loader,
    loadMore,
    pageStart,
    ref,
    threshold,
    useCapture,
    useWindow,
    getScrollParent,
    ...props
  } = renderProps;

  // 定义一个ref
  // 能将当前组件的DOM传出去
  props.ref = node => {
    this.scrollComponent = node;
    // 实行父组件传来的ref(如果有)
    if (ref) {
      ref(node);
    }
  };

  const childrenArray = [children];
  // 实行loader
  if (hasMore) {
    if (loader) {
      isReverse ? childrenArray.unshift(loader) : childrenArray.push(loader);
    } else if (this.defaultLoader) {
      isReverse
        ? childrenArray.unshift(this.defaultLoader)
        : childrenArray.push(this.defaultLoader);
    }
  }
  // ref 通报给 'div'元素
  return React.createElement(element, props, childrenArray);
}

这里一个小亮点就是,在react中,this.props是不允许修正的。

这里运用了解构

getScrollParent,
...props
} = renderProps;

这里解构相当于Object.assign,定义了一个新的object,便能够增加属性了,而且this.props不会受到影响。

总结

react-infinite-scroller逻辑比较简单。

一些注重/进修/温习点:

  • Chrome的一个转动加载要求的bug。本文位置
  • offsetParent的一些现实用法。本文位置
  • 经由过程不停定阅和作废事宜绑定让转动实行函数不会频仍触发。本文位置
  • scrollHeightclientHeight区分。本文位置

此库发起运用在自定义的一些组件上而且不那末庞杂的逻辑上。

用在第三方库能够会没法猎取准确的父组件,而经由过程document.getElementBy..传入。

面临轻微庞杂的逻辑,

比方,一个搜刮组件,定阅onChange事宜而且显现内容,搜刮”a”,对显现内容转动加载了3次,再增加搜刮词”b”,这时刻”ab”的内容显现是在3次以后。

    原文作者:stonehank
    原文地址: https://segmentfault.com/a/1190000017137730
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞