Skip to content

Conversation

@RylanBot
Copy link
Collaborator

@RylanBot RylanBot commented Oct 21, 2025

🤔 这个 PR 的性质是?

  • 日常 bug 修复
  • 新特性提交
  • 文档改进
  • 演示代码改进
  • 组件样式/交互改进
  • CI/CD 改进
  • 重构
  • 代码风格优化
  • 测试用例
  • 分支合并
  • 其他

🔗 相关 Issue

💡 需求背景和解决方案

复现代码存档
import React from 'react';
import { Tree, TreeProps } from 'tdesign-react';

const generateInitialData = () => {
  const items = [];
  for (let i = 0; i < 26; i++) {
    const letter = String.fromCharCode(65 + i); // A-Z
    items.push({
      label: `Node ${letter}`,
      value: letter,
      children: true,
    });
  }
  return items;
};

const items = generateInitialData();
const childCount = 200000;

export default () => {
  const [value, setValue] = React.useState<Array<string>>([]);
  const [loadingInfo, setLoadingInfo] = React.useState<{
    isLoading: boolean;
    nodeName: string;
    loadDuration: number;
    renderDuration: number;
    totalDuration: number;
    nodeCount: number;
  } | null>(null);

  const loadStartTimeRef = React.useRef<number>(0);
  const resolveTimeRef = React.useRef<number>(0);

  const load: TreeProps['load'] = (node) =>
    new Promise((resolve) => {
      loadStartTimeRef.current = performance.now();
      console.log(`[Load Start] Node: ${node.label}, Level: ${node.getLevel()}`);

      setLoadingInfo({
        isLoading: true,
        nodeName: node.label as string,
        loadDuration: 0,
        renderDuration: 0,
        totalDuration: 0,
        nodeCount: 0,
      });

      const timerInterval = setInterval(() => {
        const currentTime = performance.now();
        setLoadingInfo((prev) =>
          prev
            ? {
                ...prev,
                loadDuration: currentTime - loadStartTimeRef.current,
                totalDuration: currentTime - loadStartTimeRef.current,
              }
            : null,
        );
      }, 10);

      // Simulate network delay
      setTimeout(() => {
        const level = node.getLevel();
        const nodeValue = node.value as string;
        const nodes: Array<{ label: string; value: string; children?: boolean }> = [];

        if (level < 4) {
          for (let i = 1; i <= childCount; i++) {
            nodes.push({
              label: `${node.label} - Child ${i}`,
              value: `${nodeValue}-${i}`,
              children: level < 3,
            });
          }
        }

        resolveTimeRef.current = performance.now();
        const loadDuration = resolveTimeRef.current - loadStartTimeRef.current;

        clearInterval(timerInterval);

        // 先更新为加载完成状态,但继续监控渲染时间
        setLoadingInfo({
          isLoading: true, // 保持 loading 状态,因为还在渲染
          nodeName: node.label as string,
          loadDuration,
          renderDuration: 0,
          totalDuration: loadDuration,
          nodeCount: nodes.length,
        });

        console.log(`[Load Complete] Data generation took: ${loadDuration.toFixed(2)}ms`);

        resolve(nodes);

        // 使用 requestAnimationFrame 等待下一帧,然后再等待渲染完成
        requestAnimationFrame(() => {
          // 再等一帧确保渲染完成
          requestAnimationFrame(() => {
            const renderCompleteTime = performance.now();
            const totalDuration = renderCompleteTime - loadStartTimeRef.current;
            const renderDuration = renderCompleteTime - resolveTimeRef.current;

            console.log(`[Render Complete] Total time: ${totalDuration.toFixed(2)}ms`);
            console.log(`[Render Duration] Render took: ${renderDuration.toFixed(2)}ms`);

            setLoadingInfo({
              isLoading: false,
              nodeName: node.label as string,
              loadDuration,
              renderDuration,
              totalDuration,
              nodeCount: nodes.length,
            });
          });
        });
      }, 0);
    });

  const handleChange: TreeProps['onChange'] = (value) => {
    setValue(value as Array<string>);
  };

  return (
    <div>
      {loadingInfo && (
        <div
          style={{
            padding: '12px',
            marginBottom: '12px',
            backgroundColor: loadingInfo.isLoading ? '#e7f3ff' : '#f0f9ff',
            border: `1px solid ${loadingInfo.isLoading ? '#0052d9' : '#4ade80'}`,
            borderRadius: '4px',
            fontFamily: 'monospace',
            fontSize: '13px',
          }}
        >
          <div style={{ fontWeight: 'bold', marginBottom: '8px', fontSize: '14px' }}>
            {loadingInfo.isLoading ? '⏳ Loading & Rendering...' : '✅ Complete'}
          </div>
          <div style={{ marginBottom: '4px' }}>
            <strong>Node:</strong> {loadingInfo.nodeName}
          </div>
          <div style={{ marginBottom: '4px' }}>
            <strong>Generated Nodes:</strong> {loadingInfo.nodeCount.toLocaleString()}
          </div>
          <div
            style={{
              marginTop: '8px',
              paddingTop: '8px',
              borderTop: '1px solid rgba(0,0,0,0.1)',
            }}
          >
            <div style={{ marginBottom: '4px' }}>
              <strong>Data Load:</strong> {loadingInfo.loadDuration.toFixed(2)}ms
            </div>
            {!loadingInfo.isLoading && (
              <>
                <div style={{ marginBottom: '4px' }}>
                  <strong>UI Render:</strong> {loadingInfo.renderDuration.toFixed(2)}ms
                </div>
                <div
                  style={{
                    marginTop: '4px',
                    paddingTop: '4px',
                    borderTop: '1px solid rgba(0,0,0,0.1)',
                    fontWeight: 'bold',
                    color: '#0052d9',
                  }}
                >
                  <strong>Total Time:</strong> {loadingInfo.totalDuration.toFixed(2)}ms
                </div>
              </>
            )}
            {loadingInfo.isLoading && (
              <div style={{ marginTop: '4px', color: '#666' }}>
                <strong>Current:</strong> {loadingInfo.totalDuration.toFixed(2)}ms
              </div>
            )}
          </div>
        </div>
      )}
      <Tree
        value={value}
        data={items}
        checkable
        hover
        activable
        load={load}
        lazy
        onChange={handleChange}
        scroll={{ type: 'virtual' }}
        style={{ height: '500px' }}
        valueMode="all"
      />
    </div>
  );
};

📝 更新日志

  • fix(Tree): 优化大数据场景下产生的栈溢出

  • 本条 PR 不需要纳入 Changelog

☑️ 请求合并前的自查清单

⚠️ 请自检并全部勾选全部选项⚠️

  • 文档已补充或无须补充
  • 代码演示已提供或无须提供
  • TypeScript 定义已补充或无须补充
  • Changelog 已提供或无须提供

@tdesign-bot
Copy link
Collaborator

tdesign-bot commented Oct 21, 2025

TDesign Component Repositories CI Test Open

Component Lint Test Build Preview
tdesign-vue 👀
tdesign-vue-next 👀
tdesign-react 👀
tdesign-web-components 👀
tdesign-mobile-vue 👀
tdesign-mobile-react 👀

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR optimizes tree traversal and node update operations to prevent stack overflow errors when handling large datasets (e.g., 200,000+ nodes). The main changes replace recursive implementations with iterative approaches using explicit stacks and loops.

Key Changes:

  • Replaced recursive tree traversal (walk()) with iterative stack-based implementation
  • Converted Array.prototype.push.apply() and spread operators to explicit loops to avoid call stack limitations
  • Removed unnecessary array concatenation in favor of direct map operations

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
js/tree-v1/tree-store.ts Replaced spread operator with loop in refreshNodes() and optimized getRelatedNodes() to use direct map insertion instead of array concatenation
js/tree-v1/tree-node.ts Converted recursive walk() method to iterative stack-based traversal and replaced spread operators with loops in appendNodes() and remove()

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants