본문으로 건너뛰기

[Gradio] 서브탭/아코디언 컴포넌트 Lazy Loading 도입

PR 링크: gradio-app/gradio#12906 상태: Merged | 변경: +61 / -5

들어가며

Gradio 앱에서 탭(Tabs) 안에 서브탭이 중첩되거나 아코디언(Accordion) 컴포넌트가 포함된 경우, 초기 로드 시 모든 자식 컴포넌트가 한꺼번에 렌더링되었다. 사용자가 보지 않는 비활성 서브탭이나 닫힌 아코디언 내부 컴포넌트까지 전부 마운트되면서 DOM 크기가 불필요하게 커지고 초기 렌더링 시간이 증가하는 문제가 있었다. 이 PR은 비활성 상태의 서브탭과 닫힌 아코디언의 자식을 lazy load하도록 변경한다.

핵심 코드 분석

1. Visibility 판단 로직 확장 (_init.ts)

기존에는 탭 내부의 자식 컴포넌트를 일괄적으로 visible로 처리했다. 변경 후에는 서브탭의 경우 선택된 탭만 재귀적으로 visible 처리하고, 아코디언의 경우 open 상태에 따라 자식의 visibility를 결정한다.

Before:

visible_components.add(layout.id);

const child_visible = process_children_visibility(
    layout, components, parent_tabs_context
);
child_visible.forEach((id) => visible_components.add(id));

After:

visible_components.add(layout.id);

const child_visible = process_children_visibility(
    layout, components, parent_tabs_context
);
child_visible.forEach((id) => visible_components.add(id));
} else if (component.type === "accordion") {
    visible_components.add(layout.id);
    if (component.props.open !== false) {
        const child_visible = process_children_visibility(
            layout, components, parent_tabs_context
        );
        child_visible.forEach((id) => visible_components.add(id));
    }
}

아코디언이 open === false이면 자식 컴포넌트의 visibility 계산 자체를 건너뛴다.

2. 재귀 렌더링 제어 (init.svelte.ts)

make_visible_if_not_rendered 함수가 노드 타입에 따라 재귀 전략을 분기하도록 변경되었다.

Before:

function make_visible_if_not_rendered(
    node: ProcessedComponentMeta,
    hidden_on_startup: Set<number>
): void {
    node.props.shared_props.visible = hidden_on_startup.has(node.id)
        ? true : node.props.shared_props.visible;
    node.children.forEach((child) => {
        make_visible_if_not_rendered(child, hidden_on_startup);
    });
}

After:

function make_visible_if_not_rendered(
    node: ProcessedComponentMeta,
    hidden_on_startup: Set<number>,
    is_target_node = false
): void {
    node.props.shared_props.visible = hidden_on_startup.has(node.id)
        ? true : node.props.shared_props.visible;

    if (node.type === "tabs") {
        const selectedId =
            node.props.props.selected ?? node.props.props.initial_tabs?.[0]?.id;
        node.children.forEach((child) => {
            if (child.type === "tabitem" &&
                (child.props.props.id === selectedId || child.id === selectedId)) {
                make_visible_if_not_rendered(child, hidden_on_startup, false);
            }
        });
    } else if (node.type === "accordion" &&
               node.props.props.open === false && !is_target_node) {
        // Don't recurse into closed accordion content
    } else {
        node.children.forEach((child) => {
            make_visible_if_not_rendered(child, hidden_on_startup, false);
        });
    }
}

핵심은 tabs 노드에서 선택된 탭만 재귀하고, accordion이 닫혀 있으면 재귀를 차단하는 것이다. is_target_node 파라미터로 직접 타겟팅된 노드는 예외 처리한다.

왜 이게 좋은가

  • 초기 DOM 크기 감소: 비활성 서브탭과 닫힌 아코디언 내부 컴포넌트가 초기 렌더링에서 제외되어 DOM 노드 수가 줄어든다.
  • 점진적 로딩: 사용자가 탭을 클릭하거나 아코디언을 열 때 비로소 해당 컴포넌트가 렌더링된다.
  • 기존 동작 보존: is_target_node 플래그로 이벤트 핸들러에 의해 직접 visible로 전환되는 경우는 기존과 동일하게 동작한다.

정리

이 PR은 Gradio의 컴포넌트 트리 초기화 로직에 lazy loading을 도입하여, 복잡한 레이아웃(중첩 탭, 아코디언)을 가진 앱의 초기 로드 성능을 개선한다. 변경 범위가 작고(+61/-5) visibility 판단 로직만 수정했기 때문에 부작용 위험도 낮다.

참고 자료

⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.

댓글

관련 포스트

PR Analysis 의 다른글