개요 및 핵심 포인트
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 인스턴스)는 한 번 만들어 두고 재사용합니다. 청소(파괴/재생성)를 매번 한다면 안전하고 깨끗하지만 느리고, 그대로 두면 빠르지만 이전 손님의 흔적이 남을 수 있습니다.
기본 캐시 동작
Targets#display가 해당 target의viewName을 확인Views#getView가 내부_oViews캐시 맵을 조회- 캐시에 있으면 기존 인스턴스를 반환, 없으면
View.create로 새로 생성 후 등록 - 대상 컨테이너의 aggregation(보통
pages)에 추가 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
아직 댓글이 없습니다.