UI5

라우팅하면 이전 뷰는 어디로 가요? — UI5 Cache Lifecycle #shorts #SAP #UI5

▶ YouTube에서 보기

개요 및 핵심 포인트

SAPUI5/OpenUI5 애플리케이션에서 라우팅은 단순한 페이지 전환을 넘어 뷰 인스턴스의 생명주기(Lifecycle)를 직접 제어하는 핵심 메커니즘입니다. 기본적으로 UI5 라우터는 한 번 생성된 뷰를 메모리에 캐시(cache)하여 재방문 시 빠르게 보여주지만, 이는 곧 stale 데이터(오래된 데이터)나 메모리 누수 같은 부작용으로 이어질 수 있습니다. 본 문서는 clearControlAggregation, 라이프사이클 훅(onBeforeShow 계열), cacheIdentifier 세 가지 도구를 단계적으로 다루어 실무에서 캐시 전략을 적절히 선택할 수 있도록 정리합니다.

  • 라우터의 기본 캐시 동작과 그 trade-off 이해
  • manifest.json target 옵션을 통한 캐시 폐기 제어
  • 뷰가 표시/숨김 처리될 때 발생하는 훅 활용
  • 동일 타깃을 파라미터별로 다중 인스턴스화하는 식별자 패턴
  • 메모리 누수와 이벤트 중복 등록 방지 전략

사전에 알아두면 좋은 배경

본 내용을 따라가려면 SAPUI5 라우팅 기본 구조(sap.ui.core.routing.Router, Targets, Views), MVC 패턴, Controller.extend를 통한 컨트롤러 정의 경험이 필요합니다. 또한 manifest.json의 sap.ui5 섹션 구성과 NavContainer/App/SplitApp 같은 컨테이너 컨트롤의 차이를 알고 있으면 좋습니다.

실습 환경 및 버전

  • SAPUI5 / OpenUI5: 1.108 LTS 이상 (1.120 권장)
  • Node.js: 18 이상 (UI5 Tooling 3.x)
  • IDE: SAP Business Application Studio 또는 VS Code + ui5-language-assistant
  • 실행: npx ui5 serve -o index.html

핵심 개념과 동작 원리

UI5 라우터를 호텔 프런트에 비유하면 이해가 쉽습니다. 손님(URL hash)이 도착하면 프런트(Router)는 객실(Target)을 배정하고, 객실 안의 인테리어(View 인스턴스)는 한 번 만들어 두고 재사용합니다. 청소(파괴/재생성)를 매번 한다면 안전하고 깨끗하지만 느리고, 그대로 두면 빠르지만 이전 손님의 흔적이 남을 수 있습니다.

기본 캐시 동작

  1. Targets#display가 해당 target의 viewName을 확인
  2. Views#getView가 내부 _oViews 캐시 맵을 조회
  3. 캐시에 있으면 기존 인스턴스를 반환, 없으면 View.create로 새로 생성 후 등록
  4. 대상 컨테이너의 aggregation(보통 pages)에 추가
  5. to(view) 호출 → onBefore/AfterShow 이벤트 발생

여기서 핵심은 "뷰는 한 번만 만들어진다"는 점입니다. 따라서 onInit은 최초 1회만 호출되고, 라우트 파라미터를 다시 읽으려면 patternMatched 이벤트나 라이프사이클 훅을 따로 구독해야 합니다.

캐시의 장단점

  • 장점: 빠른 재방문, DOM 재생성 비용 절감, 스크롤 위치/입력값 유지
  • 단점: 백엔드 데이터 변경 미반영, 폼 상태 누적, 이벤트 핸들러 중복 등록 위험

실전 코드

1단계 — clearControlAggregation으로 캐시 무효화

clearControlAggregation은 manifest.json의 target 옵션으로, true로 설정하면 해당 target으로 이동할 때마다 컨테이너의 aggregation을 비우고 뷰를 새 인스턴스로 교체합니다. 기본값은 false이며, 대부분의 목록 뷰는 false 유지가 적절합니다. 반면 폼 입력 화면이나 결제 시작 화면처럼 매번 초기 상태가 필요한 경우 true가 유용합니다.

{
  "sap.ui5": {
    "routing": {
      "config": {
        "routerClass": "sap.m.routing.Router",
        "viewType": "XML",
        "controlId": "appNavContainer",
        "controlAggregation": "pages",
        "async": true
      },
      "targets": {
        "list":   { "viewName": "List",   "viewLevel": 1 },
        "detail": { "viewName": "Detail", "viewLevel": 2 },
        "create": {
          "viewName": "Create",
          "viewLevel": 2,
          "clearControlAggregation": true
        }
      }
    }
  }
}

create target은 진입할 때마다 NavContainer의 pages가 비워지고 새 Create 뷰가 생성됩니다. 같은 컨테이너를 공유하는 형제 뷰가 있다면 영향 범위를 주의해야 합니다.

2단계 — 라이프사이클 훅으로 데이터 갱신

캐시를 유지하면서도 매번 최신 데이터를 보여주려면 onBeforeShow/onAfterShow/onBeforeHide/onAfterHide 컨트롤러 메서드를 활용합니다.

return Controller.extend("com.example.app.controller.Detail", {

  onInit: function () {
    // 최초 1회만 — 정적 초기화
    this.getOwnerComponent().getRouter()
      .getRoute("detail")
      .attachPatternMatched(this._onRouteMatched, this);
  },

  _onRouteMatched: function (oEvent) {
    this._sCurrentId = oEvent.getParameter("arguments").id;
  },

  onBeforeShow: function () {
    // 캐시된 뷰가 다시 표시될 때마다 실행 — 데이터 fetch
    if (this._sCurrentId) {
      this._loadDetail(this._sCurrentId);
    }
  },

  onAfterHide: function () {
    // 숨김 후 — 타이머·리소스 정리
    this._clearTimers();
  }
});

주의: onBeforeShow는 NavContainer 계열에서 자동 호출됩니다. manifest의 controlId가 NavContainer 또는 서브클래스를 가리켜야 훅이 동작합니다.

3단계 — cacheIdentifier로 다중 인스턴스 관리

동일 타깃을 파라미터별로 독립 캐시하려면 display() 호출 시 prefix로 고유 키를 부여합니다.

// 탭별 독립 캐시
onOpenInTab: function (sItemId) {
  this.getOwnerComponent().getTargets().display({
    name: "detail",
    prefix: "item-" + sItemId,
    data: { id: sItemId },
    routeRelevant: false
  });
},

// 뒤로가기 시 상태 복원
onNavBack: function () {
  var sPrev = sap.ui.core.routing.History
    .getInstance().getPreviousHash();
  if (sPrev !== undefined) {
    window.history.go(-1);
  } else {
    this.getOwnerComponent().getRouter()
      .navTo("list", {}, true);
  }
}

인스턴스가 누적되므로 N개를 초과하면 가장 오래된 것을 destroy()로 정리하는 LRU 로직을 컴포넌트에 추가하세요.

실전 패턴 — 목록/상세 화면의 캐시 전략

  • List(목록): clearControlAggregation: false + onBeforeShow에서 조건부 refresh. 스크롤 위치 유지 중요.
  • Detail(상세): 캐시 유지 + patternMatched에서 ID별 binding 갱신.
  • Create/Edit(폼): clearControlAggregation: true 또는 onAfterHide에서 reset. stale 입력값 방지.
  • Dashboard: 캐시 유지 + 타임스탬프 비교 후 일정 시간 경과 시만 재조회.

흔한 실수와 트러블슈팅

  • 이벤트 핸들러 중복 등록: onBeforeShow에서 attach 반복 금지. attach는 onInit에서만.
  • clearControlAggregation 부수효과: aggregation 전체를 비우므로 컨테이너 분리 또는 직접 destroy 패턴으로 대체 검토.
  • 민감 정보 잔류: onAfterHide에서 모델 초기화 또는 oView.destroy() 직접 호출.
  • cacheIdentifier 인스턴스 누적: 라우터가 자동 회수하지 않으므로 LRU 정리 로직 필수.
  • onBeforeShow 미호출: controlId가 NavContainer 계열인지 확인.

다음으로 확장해볼 주제

캐시 전략을 익혔다면 Targets의 parent 옵션을 이용한 중첩 뷰 캐시 구조, FlexibleColumnLayout과의 결합, OData V4 ListBinding refresh API로 캐시 유지 + 데이터 갱신 조합, OPA5 라우팅 시나리오 자동화로 학습을 확장해보세요.

참고 자료

댓글 0

아직 댓글이 없습니다.