접근성과 상태 흐름을 익혔다면, 자주 등장하는 UI를 패턴 단위로 묶어 중복을 줄여야 합니다. 패턴은 “상태를 받아 DOM을 갱신하는 렌더 함수”로 정의할 수 있습니다.
이번 글에서 새로 나오는 용어
- 패턴 카탈로그: 반복적으로 쓰이는 카드·리스트 같은 UI 조각을 문서로 정리한 목록입니다.
- 부분 갱신: 전체 리스트 대신 바뀐 항목만 찾아 교체해 성능을 지키는 업데이트 방식입니다.
- dataset:
data-id처럼 HTML에 메타 정보를 저장해 JS에서element.dataset.id로 읽는 속성입니다. - 템플릿 헬퍼: 공통 렌더 규칙(마운트, 이벤트 연결 등)을 묶어 반복을 줄여 주는 함수입니다.
핵심 개념
- 렌더 함수(Render Function, 상태 → 뷰 변환 함수): 상태를 입력으로 받아 HTML 문자열이나 DOM 조작을 출력하는 순수 함수입니다. 입력이 같으면 출력도 같아야 합니다.
- 패턴 카탈로그(Pattern Catalog, 반복 UI 목록): 카드, 리스트, 배너, 모달 등 반복되는 구조를 목록으로 정리한 문서입니다. 필요한 상태, 이벤트, 접근성 속성을 함께 기록합니다.
- 부분 갱신(Partial Update, 필요한 조각만 갱신): 리스트 전체를 다시 그리는 대신 변경된 아이템만 업데이트하는 패턴입니다.
dataset이나id를 활용합니다. - 뷰/상태 분리(View-State Separation, 역할 분리): 렌더 함수에는 비즈니스 로직이 없어야 유지보수가 쉽습니다. 데이터 가공은 다른 함수에서 담당합니다.
코드로 확인하기
먼저 카드와 리스트 패턴을 순수 함수로 정의합니다.
function renderCard({ title, summary, actions = [] }) {
return `
<article class="card">
<h3>${title}</h3>
<p>${summary}</p>
<div class="card-actions">
${actions.map((action) => `<button data-action="${action.id}">${action.label}</button>`).join("")}
</div>
</article>
`;
}
function renderList({ items, emptyMessage = "항목이 없습니다." }) {
if (items.length === 0) {
return `<p class="list-empty" role="status">${emptyMessage}</p>`;
}
return `
<ul class="list" role="list">
${items.map((item) => `<li data-id="${item.id}">${renderCard(item)}</li>`).join("")}
</ul>
`;
}
const listRoot = document.querySelector("#project-list");
function updateProjectList(projects) {
if (!listRoot) return;
listRoot.innerHTML = renderList({ items: projects });
}
function updateSingleProject(project) {
const target = listRoot?.querySelector(`[data-id="${project.id}"]`);
if (!target) return;
target.outerHTML = `<li data-id="${project.id}">${renderCard(project)}</li>`;
}
updateProjectList는 전체를 렌더링하고 updateSingleProject는 부분 갱신을 담당합니다.
이제 패턴 렌더러 헬퍼를 만들어 템플릿과 마운트 규칙을 통일합니다.
function createPatternRenderer({ root, template, onMount }) {
let currentState = null;
function render(state) {
currentState = state;
root.innerHTML = template(state);
onMount?.({ root, state: currentState });
}
return { render, getState: () => currentState };
}
const banner = createPatternRenderer({
root: document.querySelector(".alert-banner"),
template: ({ type, message }) => `
<div class="alert alert--${type}" role="alert">
${message}
<button class="alert-close" aria-label="배너 닫기">×</button>
</div>
`,
onMount: ({ root }) => {
root.querySelector(".alert-close")?.addEventListener("click", () => {
root.innerHTML = "";
});
},
});
banner.render({ type: "info", message: "저장이 완료되었습니다." });
왜 중요한가
- 패턴 단위로 코드를 나누면 팀원이 합류했을 때 “이 UI는 renderList 패턴을 따라야 한다”라고 설명하기 쉬워집니다.
- 부분 갱신은 대량의 데이터를 다룰 때 성능을 안정적으로 유지하는 비결입니다.
- 패턴 카탈로그는 디자인 시스템으로 확장할 기초가 됩니다. Figma 컴포넌트와 코드가 같은 이름을 사용하도록 맞출 수 있습니다.
실습
- 따라 하기: 카드, 리스트, 배너 패턴을 각각 렌더 함수로 만들고 동일한 상태를 넣어 결과를 비교합니다.
- 확장하기:
createPatternRenderer와 같은 헬퍼를 만들어render,onMount구조를 통일합니다. - 디버깅: 렌더 함수 안에서 상태를 직접 수정하도록 일부러 실수를 만들고, 왜 버그가 생기는지 기록한 뒤 순수 함수로 되돌립니다.
- 완료 기준: 최소 세 가지 패턴이 같은 상태 구조를 받아 렌더링하고, 한 패턴에서 부분 갱신 함수가 정상 동작하면 실습이 끝납니다.
마무리
재사용 가능한 패턴을 갖추면 UI를 확장할 때 속도가 붙습니다. 다음 글에서는 API 에러 상태와 네트워크 메시지를 이 패턴 위에 어떻게 올릴지 살펴보겠습니다.
💬 댓글
이 글에 대한 의견을 남겨주세요