在 Taro 中实现懒加载组件:保留元素高度的解决方案

在开发小程序时,我们常常需要处理长列表的渲染问题。为了优化性能,组件懒加载是一种非常有效的手段。然而,在实现懒加载时,我们需要确保不可见的元素仍然保留它们的高度,以避免位置变化。这篇文章将记录我在 Taro 中实现懒加载组件的过程,并分享我的解决方案。

问题描述

在我的小程序项目中,我需要渲染一个包含大量项目的长列表。为了提高性能,我决定使用懒加载技术,只在需要时加载和渲染可见的项目。然而,我遇到了一个问题:当项目不可见时,它们的高度会消失,导致整个列表的位置发生变化。这种情况会影响用户体验,特别是在快速滚动时。

解决方案

为了解决这个问题,我决定创建一个懒加载组件,该组件在不可见时保留元素的高度。具体步骤如下:

  1. 监听元素是否进入视口:使用 Taro.createIntersectionObserver 创建一个观察器。
  2. 动态获取元素高度:使用 Taro.createSelectorQuery 获取元素的高度。
  3. 保留高度:当元素不可见时,通过 paddingTop 保留高度,避免位置变化。

代码实现

1. 创建 LazyLoadComponent 组件

首先,我们创建一个 LazyLoadComponent 组件,用于包裹需要懒加载的内容。

import React, { useEffect, useRef, useState } from 'react';
import Taro from '@tarojs/taro';
import { View } from '@tarojs/components';

interface LazyLoadComponentProps {
  children: React.ReactNode;
  threshold: number; // 触发加载的阈值,需要大于100
}

function LazyLoadComponent(props: LazyLoadComponentProps) {
  const { children, threshold } = props;
  const [isVisible, setIsVisible] = useState(true);
  const [elementHeight, setElementHeight] = useState(0);
  const containerRef = useRef<HTMLDivElement>(null);
  const lazyloadContainerId = useRef(`lazyload-${Math.random().toString(36).slice(2)}`);

  useEffect(() => {
    if (!containerRef.current) return () => {};

    getElementBoundingClientRect(`#${lazyloadContainerId.current}`).then((res) => {
      setElementHeight(res?.height!);
    });

    // 创建 IntersectionObserver
    const observer = Taro.createIntersectionObserver(this, {
      thresholds: [0.1],
      observeAll: false,
    });

    observer.relativeToViewport({ top: threshold }).observe(`#${lazyloadContainerId.current}`, (res) => {
      if (res.intersectionRatio > 0.1) {
        setIsVisible(true);
      } else {
        setIsVisible(false);
      }
    });

    return () => {
      observer.disconnect();
    };
  }, [threshold]);

  return (
    <View
      id={lazyloadContainerId.current}
      ref={containerRef}
      style={{
        width: isVisible ? 'auto' : '1px',
        height: isVisible ? 'auto' : `${elementHeight}px`,
      }}
    >
      {isVisible ? children : null}
    </View>
  );
}

export default LazyLoadComponent;
2. 获取元素高度

为了获取每个元素的高度,我们定义了一个辅助函数 getElementBoundingClientRect。这个函数使用了 Taro.createSelectorQuery 来查询元素的边界矩形,并返回一个 Promise

function getElementBoundingClientRect(selector: string) {
  return new Promise<BoundingClientRect | undefined>((resolve, reject) => {
    function getBounding() {
      const $ = Taro.createSelectorQuery();
      $.select(selector)
        .boundingClientRect()
        .exec((res) => {
          if (res && res[0]) {
            resolve(res[0]);
          } else {
            resolve(undefined);
          }
        });
    }

    try {
      const timer = setTimeout(() => {
        getBounding();
      }, 16);
      Taro.nextTick(() => {
        clearTimeout(timer);
        getBounding();
      });
    } catch (e) {
      reject(e);
    }
  });
}
3. 使用示例

我们在一个长列表中使用 LazyLoadComponent 来包裹需要懒加载的内容。

import React from 'react';
import LazyLoadComponent from './LazyLoadComponent';

const LongList = () => {
  const items = Array.from({ length: 10000 }, (_, index) => index + 1);

  return (
    <View>
      {items.map(item => (
        <LazyLoadComponent key={item} threshold={300}>
          <View style={{ height: '100px', border: '1px solid black', margin: '10px 0' }}>
            Item {item}
          </View>
        </LazyLoadComponent>
      ))}
    </View>
  );
};

export default LongList;

代码解释

  1. 获取元素高度:使用 Taro.createSelectorQuery 获取元素的高度,并更新 elementHeight 状态。
  2. IntersectionObserver:使用 Taro.createIntersectionObserver 创建一个观察器,监听元素是否进入视口。
  3. 保留高度:当元素不可见时,通过 paddingTop 保留高度,避免位置变化。

通过上述步骤,我们成功实现了在 Taro 中的懒加载组件,并解决了不可见元素高度消失的问题。希望这篇文章对你有所帮助!