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

如何判断React-Bootstrap组件是否完成渲染?解决折叠面板加载异常

解决React Bootstrap Accordion大数据渲染前展开异常问题

针对本地大数据量导致Accordion渲染耗时、未完成前展开引发功能异常的问题,可通过监听内容DOM挂载状态+控制交互权限/显示加载遮罩的方式解决,以下是函数式组件的具体实现方案:

核心思路

本地数据已就绪,但DOM渲染耗时,因此需要:

  • 用状态标记内容是否渲染完成
  • 渲染完成前,禁止Accordion展开操作或用加载遮罩覆盖面板
  • 通过useRef+useEffect监听内容DOM的挂载,触发渲染完成状态更新

实现代码

基础版本:渲染完成前禁用展开+显示遮罩

import { useState, useEffect, useRef } from 'react';
import { Accordion, AccordionItem, AccordionHeader, AccordionBody, Spinner } from 'react-bootstrap';

const LargeDataAccordion = () => {
  const [isRendered, setIsRendered] = useState(false);
  const [isOpen, setIsOpen] = useState(false);
  const contentRef = useRef(null);

  // 模拟本地大数据集合
  const largeData = Array.from({ length: 10000 }, (_, i) => `列表项 ${i + 1}`);

  // 监听内容DOM是否挂载完成
  useEffect(() => {
    if (contentRef.current) {
      setIsRendered(true);
    }
  }, []);

  // 仅允许渲染完成后切换展开状态
  const handleToggle = () => {
    if (isRendered) {
      setIsOpen(!isOpen);
    }
  };

  return (
    <div style={{ position: 'relative' }}>
      {/* 加载遮罩:渲染完成前覆盖面板,阻止操作 */}
      {!isRendered && (
        <div style={{
          position: 'absolute',
          inset: 0,
          backgroundColor: 'rgba(255,255,255,0.9)',
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center',
          zIndex: 10
        }}>
          <Spinner animation="border" />
        </div>
      )}

      <Accordion activeKey={isOpen ? '0' : null} onToggle={handleToggle}>
        <AccordionItem eventKey="0">
          <AccordionHeader>
            <button 
              type="button" 
              disabled={!isRendered}
              style={{ pointerEvents: !isRendered ? 'none' : 'auto' }}
            >
              展开面板 {!isRendered && '(加载中...)'}
            </button>
          </AccordionHeader>
          <AccordionBody ref={contentRef}>
            {largeData.map((item, index) => (
              <div key={index}>{item}</div>
            ))}
          </AccordionBody>
        </AccordionItem>
      </Accordion>
    </div>
  );
};

export default LargeDataAccordion;

优化版本:分批渲染减少初始耗时

如果数据量极大,可通过分批渲染降低单次DOM操作的耗时,同时逐步更新内容:

import { useState, useEffect, useRef } from 'react';
import { Accordion, AccordionItem, AccordionHeader, AccordionBody, Spinner } from 'react-bootstrap';

const LargeDataAccordion = () => {
  const [isRendered, setIsRendered] = useState(false);
  const [isOpen, setIsOpen] = useState(false);
  const [renderedItems, setRenderedItems] = useState([]);
  const [currentBatch, setCurrentBatch] = useState(0);
  const contentRef = useRef(null);
  const batchSize = 200; // 每批渲染数量

  // 模拟本地大数据集合
  const largeData = Array.from({ length: 10000 }, (_, i) => `列表项 ${i + 1}`);

  // 分批渲染逻辑
  useEffect(() => {
    const loadNextBatch = () => {
      const start = currentBatch * batchSize;
      const end = start + batchSize;
      const newItems = largeData.slice(start, end);
      
      if (newItems.length > 0) {
        setRenderedItems(prev => [...prev, ...newItems]);
        setCurrentBatch(prev => prev + 1);
      } else {
        setIsRendered(true);
      }
    };

    // 首次渲染+后续分批加载
    const timer = setTimeout(loadNextBatch, 0);
    return () => clearTimeout(timer);
  }, [currentBatch, largeData.length]);

  const handleToggle = () => {
    if (isRendered) {
      setIsOpen(!isOpen);
    }
  };

  return (
    <div style={{ position: 'relative' }}>
      {!isRendered && (
        <div style={{
          position: 'absolute',
          inset: 0,
          backgroundColor: 'rgba(255,255,255,0.9)',
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center',
          zIndex: 10
        }}>
          <Spinner animation="border" />
          <span style={{ marginLeft: '8px' }}>已加载 {renderedItems.length}/{largeData.length}</span>
        </div>
      )}

      <Accordion activeKey={isOpen ? '0' : null} onToggle={handleToggle}>
        <AccordionItem eventKey="0">
          <AccordionHeader>
            <button 
              type="button" 
              disabled={!isRendered}
              style={{ pointerEvents: !isRendered ? 'none' : 'auto' }}
            >
              展开面板 {!isRendered && '(加载中...)'}
            </button>
          </AccordionHeader>
          <AccordionBody ref={contentRef}>
            {renderedItems.map((item, index) => (
              <div key={index}>{item}</div>
            ))}
          </AccordionBody>
        </AccordionItem>
      </Accordion>
    </div>
  );
};

export default LargeDataAccordion;

关键说明

  • contentRef绑定AccordionBody,当DOM挂载完成后,useEffect会触发并设置isRendered为true
  • 加载遮罩使用绝对定位覆盖面板,既提示用户加载状态,也阻止误操作
  • 分批渲染通过拆分数据集合,降低单次DOM渲染的压力,缩短整体等待时间

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 00:05:31