El código fuente que todos pueden entender, además de useVirtualList, encapsula una lista de desplazamiento virtual

¡Trabajar juntos para crear y crecer juntos! Este es el segundo día de mi participación en el "Nuggets Daily New Plan · August Update Challenge", haz clic para ver los detalles del evento

Este artículo es el decimoctavo de una serie de artículos que explican el código fuente de ahoos en un lenguaje sencillo, que se ha organizado en dirección- documento . Creo que no está mal, dale una estrella para apoyarlo, gracias.

Introducción

Los ganchos que brindan capacidades de listas virtualizadas se utilizan para resolver los problemas de procesamiento lento de la primera pantalla y bloqueos de desplazamiento al procesar datos masivos.

Los detalles se pueden encontrar en el sitio web oficial , y el código fuente del artículo se puede encontrar aquí .

Principio de implementación

Su principio de implementación monitorea el evento de desplazamiento del contenedor externo y cuando cambia su tamaño, activando la lógica de cálculo para calcular el valor de altura y marginTop del contenedor interno.

Implementación

Su lógica de desplazamiento de seguimiento es la siguiente:

// 当外部容器的 size 发生变化的时候,触发计算逻辑
useEffect(() => {
  if (!size?.width || !size?.height) {
    return;
  }
  // 重新计算逻辑
  calculateRange();
}, [size?.width, size?.height, list]);

// 监听外部容器的 scroll 事件
useEventListener(
  'scroll',
  e => {
    // 如果是直接跳转,则不需要重新计算
    if (scrollTriggerByScrollToFunc.current) {
      scrollTriggerByScrollToFunc.current = false;
      return;
    }
    e.preventDefault();
    // 计算
    calculateRange();
  },
  {
    // 外部容器
    target: containerTarget,
  },
);
复制代码

Entre ellos,calculateRange es muy importante.Básicamente implementa la lógica principal del proceso de desplazamiento virtual.Principalmente hace las siguientes cosas:

  • Obtiene la altura total de todo el contenedor interno.
  • Calcule cuántos elementos se han "desplazado" de acuerdo con el scrollTop del contenedor exterior, el valor está compensado.
  • De acuerdo con la altura del contenedor exterior y el índice de inicio actual, obtenga el visibleCount que puede transportar el contenedor exterior.
  • Y calcule el índice de inicio (inicio) y (final) de acuerdo con la sobreexploración (la cantidad de nodos DOM que se muestran arriba y debajo de la ventana gráfica).
  • Obtenga la distancia al comienzo de su distancia (offsetTop) de acuerdo con el índice de inicio.
  • Finalmente, configure la altura y el margen superior del contenedor interno de acuerdo con offsetTop y totalHeight.

Hay muchas variables, que se pueden entender claramente combinando la siguiente figura:

imagen

el código se muestra a continuación:

// 计算范围,由哪个开始,哪个结束
const calculateRange = () => {
  // 获取外部和内部容器
  // 外部容器
  const container = getTargetElement(containerTarget);
  // 内部容器
  const wrapper = getTargetElement(wrapperTarget);

  if (container && wrapper) {
    const {
      // 滚动距离顶部的距离。设置或获取位于对象最顶端和窗口中可见内容的最顶端之间的距离
      scrollTop,
      // 内容可视区域的高度
      clientHeight,
    } = container;

    // 根据外部容器的 scrollTop 算出已经“滚过”多少项
    const offset = getOffset(scrollTop);
    // 可视区域的 DOM 个数
    const visibleCount = getVisibleCount(clientHeight, offset);

    // 开始的下标
    const start = Math.max(0, offset - overscan);
    // 结束的下标
    const end = Math.min(list.length, offset + visibleCount + overscan);

    // 获取上方高度
    const offsetTop = getDistanceTop(start);
    // 设置内部容器的高度,总的高度 - 上方高度
    // @ts-ignore
    wrapper.style.height = totalHeight - offsetTop + 'px';
    // margin top 为上方高度
    // @ts-ignore
    wrapper.style.marginTop = offsetTop + 'px';
    // 设置最后显示的 List
    setTargetList(
      list.slice(start, end).map((ele, index) => ({
        data: ele,
        index: index + start,
      })),
    );
  }
};
复制代码

Otras son las funciones auxiliares de esta función, que incluyen:

  • Calcule la cantidad en el área visible en función de la altura del contenedor exterior y cada elemento del interior:
// 根据外部容器以及内部每一项的高度,计算出可视区域内的数量
const getVisibleCount = (containerHeight: number, fromIndex: number) => {
  // 知道每一行的高度 - number 类型,则根据容器计算
  if (isNumber(itemHeightRef.current)) {
    return Math.ceil(containerHeight / itemHeightRef.current);
  }

  // 动态指定每个元素的高度情况
  let sum = 0;
  let endIndex = 0;
  for (let i = fromIndex; i < list.length; i++) {
    // 计算每一个 Item 的高度
    const height = itemHeightRef.current(i, list[i]);
    sum += height;
    endIndex = i;
    // 大于容器宽度的时候,停止
    if (sum >= containerHeight) {
      break;
    }
  }
  // 最后一个的下标减去开始一个的下标
  return endIndex - fromIndex;
};
复制代码
  • Calcule cuántos nodos DOM hay según scrollTop:
// 根据 scrollTop 计算上面有多少个 DOM 节点
const getOffset = (scrollTop: number) => {
  // 每一项固定高度
  if (isNumber(itemHeightRef.current)) {
    return Math.floor(scrollTop / itemHeightRef.current) + 1;
  }
  // 动态指定每个元素的高度情况
  let sum = 0;
  let offset = 0;
  // 从 0 开始
  for (let i = 0; i < list.length; i++) {
    const height = itemHeightRef.current(i, list[i]);
    sum += height;
    if (sum >= scrollTop) {
      offset = i;
      break;
    }
  }
  // 满足要求的最后一个 + 1
  return offset + 1;
};
复制代码
  • Obtener la altura superior:
// 获取上部高度
const getDistanceTop = (index: number) => {
  // 每一项高度相同
  if (isNumber(itemHeightRef.current)) {
    const height = index * itemHeightRef.current;
    return height;
  }
  // 动态指定每个元素的高度情况,则 itemHeightRef.current 为函数
  const height = list
    .slice(0, index)
    // reduce 计算总和
    // @ts-ignore
    .reduce((sum, _, i) => sum + itemHeightRef.current(i, list[index]), 0);
  return height;
};
复制代码
  • Calcular la altura total:
// 计算总的高度
const totalHeight = useMemo(() => {
  // 每一项高度相同
  if (isNumber(itemHeightRef.current)) {
    return list.length * itemHeightRef.current;
  }
  // 动态指定每个元素的高度情况
  // @ts-ignore
  return list.reduce(
    (sum, _, index) => sum + itemHeightRef.current(index, list[index]),
    0,
  );
}, [list]);
复制代码

Finalmente, se expone una función que se desplaza al índice especificado, que principalmente calcula la altura scrollTop desde la parte superior del índice y la establece en el contenedor externo. Y active la función de cálculo del rango.

// 滚动到指定的 index
const scrollTo = (index: number) => {
  const container = getTargetElement(containerTarget);
  if (container) {
    scrollTriggerByScrollToFunc.current = true;
    // 滚动
    container.scrollTop = getDistanceTop(index);
    calculateRange();
  }
};
复制代码

pensar y resumir

En el caso de que la altura sea relativamente segura, es relativamente sencillo para nosotros hacer un desplazamiento virtual, pero ¿y si la altura es incierta?

O otro ángulo, cuando nuestro desplazamiento no es vertical, sino horizontal, ¿cómo lidiar con eso?

Este artículo ha sido incluido en el blog personal , bienvenido a prestar atención~

Supongo que te gusta

Origin juejin.im/post/7132842601786376200
Recomendado
Clasificación