You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React组件中实现滚动至页面70%时分页API数据加载方案

嘿,这个需求在React里有不少优雅的实现方式,我最推荐用Intersection Observer API——它比传统的滚动监听性能好太多,不用频繁计算滚动位置,还能精准控制触发时机。当然也给你准备了兼容旧场景的传统方案,你可以按需选择。

方案一:Intersection Observer(首推)

这个API是浏览器原生提供的,专门用来监听元素是否进入(或即将进入)视口。我们可以在列表底部放一个“哨兵”元素,通过配置提前触发的距离,刚好对应你要的滚动到70%时加载下一页的需求。

基于你的组件代码修改后的完整实现:

import React, { Component } from 'react';
import Card from './Card';

class Main extends Component {
  constructor(props) {
    super(props);
    this.url = 'https://rickandmortyapi.com/api/character';
    this.sentinelRef = React.createRef(); // 用于监听的哨兵元素ref
    this.observer = null;
    this.state = {
      data: [],
      nextPageUrl: null,
      isLoading: false,
      error: null
    };
  }

  componentDidMount() {
    // 初始化观察者,配置提前30%视口高度触发(对应滚动到70%时触发)
    this.observer = new IntersectionObserver(
      (entries) => {
        const [entry] = entries;
        // 当哨兵进入触发区域、当前未加载、还有下一页时,请求数据
        if (entry.isIntersecting && !this.state.isLoading && this.state.nextPageUrl) {
          this.fetchNextPage();
        }
      },
      { rootMargin: '30% 0px', threshold: 0 }
    );

    // 开始监听哨兵元素
    if (this.sentinelRef.current) {
      this.observer.observe(this.sentinelRef.current);
    }

    // 加载初始数据
    this.fetchInitialData();
  }

  componentWillUnmount() {
    // 销毁观察者,避免内存泄漏
    if (this.observer && this.sentinelRef.current) {
      this.observer.unobserve(this.sentinelRef.current);
    }
  }

  // 加载第一页数据
  fetchInitialData = async () => {
    try {
      this.setState({ isLoading: true });
      const res = await fetch(this.url);
      const result = await res.json();
      this.setState({
        data: result.results,
        nextPageUrl: result.info.next,
        isLoading: false
      });
    } catch (err) {
      this.setState({ error: err.message, isLoading: false });
    }
  };

  // 加载下一页数据
  fetchNextPage = async () => {
    if (!this.state.nextPageUrl) return;

    try {
      this.setState({ isLoading: true });
      const res = await fetch(this.state.nextPageUrl);
      const result = await res.json();
      this.setState(prev => ({
        data: [...prev.data, ...result.results],
        nextPageUrl: result.info.next,
        isLoading: false
      }));
    } catch (err) {
      this.setState({ error: err.message, isLoading: false });
    }
  };

  render() {
    const { data, isLoading, error } = this.state;

    if (error) return <div className="error">加载出错:{error}</div>;

    return (
      <div style={{ minHeight: '100vh' }}>
        <div className="card-grid">
          {data.map(item => <Card key={item.id} data={item} />)}
        </div>
        {/* 加载状态提示 */}
        {isLoading && <div className="loading" style={{ textAlign: 'center', padding: '2rem' }}>加载中...</div>}
        {/* 哨兵元素:透明不可见,用于触发加载 */}
        <div ref={this.sentinelRef} style={{ height: '1px', opacity: 0 }} />
      </div>
    );
  }
}

export default Main;

关键说明:

  • rootMargin: '30% 0px':配置这个参数后,当哨兵元素距离视口底部还有30%的视口高度时,就会触发回调——刚好对应页面滚动到70%的位置。
  • 哨兵元素是一个透明的占位元素,完全不影响页面UI。
  • 通过isLoading状态防止重复请求,避免滚动时多次触发加载。

方案二:传统滚动监听(兼容旧场景)

如果需要支持不兼容Intersection Observer的旧浏览器,可以用滚动监听方案,不过记得加防抖优化性能:

修改你的组件中的生命周期和滚动处理逻辑:

constructor(props) {
  super(props);
  this.url = 'https://rickandmortyapi.com/api/character';
  this.handleScroll = this.debounce(this.handleScroll, 100); // 防抖处理
  this.state = {
    data: [],
    nextPageUrl: null,
    isLoading: false,
    error: null
  };
}

componentDidMount() {
  window.addEventListener('scroll', this.handleScroll);
  this.fetchInitialData();
}

componentWillUnmount() {
  window.removeEventListener('scroll', this.handleScroll);
}

// 防抖函数:减少滚动事件触发频率
debounce = (func, delay) => {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
};

// 滚动处理逻辑
handleScroll = () => {
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  const windowHeight = window.innerHeight;
  const documentHeight = document.documentElement.scrollHeight;
  // 计算当前滚动的百分比
  const scrollPercent = (scrollTop + windowHeight) / documentHeight;

  // 当滚动到70%以上、未加载、还有下一页时触发
  if (scrollPercent >= 0.7 && !this.state.isLoading && this.state.nextPageUrl) {
    this.fetchNextPage();
  }
};

注意点:

  • 滚动事件会频繁触发,必须加防抖(比如延迟100ms)来减少不必要的计算和请求。
  • 这种方式需要自己计算滚动位置,代码相对繁琐,性能也不如Intersection Observer。

总的来说,优先选择Intersection Observer方案,它更符合现代前端的性能优化思路,代码也更简洁易维护。

内容的提问来源于stack exchange,提问作者user6147554

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:25:31