Once you understand accessibility and state flow, bundle repeating UI into patterns to reduce duplication. A pattern is “a render function that receives state and updates the DOM”.
Key terms
- Pattern catalog: the documented list of reusable UI fragments—cards, lists, banners—that details the required state, events, and accessibility hooks.
- Partial update: a performance-friendly strategy that replaces only the changed item instead of rerendering entire lists.
dataset: a property for reading metadata such asdata-idviaelement.dataset.id.- Template helper: a function that bundles shared render rules (mounting, event wiring, cleanup) to reduce repetition.
Core ideas
- Render function: a pure mapping from state to HTML or DOM operations; identical input must yield identical output.
- Pattern catalog: enumerate repeated structures (cards, lists, banners, modals) and record their state needs, events, and accessibility attributes.
- Partial update: update only the affected list item with an identifier, typically using
datasetoridattributes. - View/state separation: keep business logic outside render functions so maintenance stays easy.
Card and list patterns
Define cards and lists as pure functions first.
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 = "No items yet." }) {
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 re-renders everything, while updateSingleProject powers partial updates.
Building our reusable UI helper
Now create a helper that unifies template rendering and mounting rules.
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="Close banner">×</button>
</div>
`,
onMount: ({ root }) => {
root.querySelector(".alert-close")?.addEventListener("click", () => {
root.innerHTML = "";
});
},
});
banner.render({ type: "info", message: "Save completed." });
Why it matters
- Splitting code by pattern gives teammates a shared language: “this screen uses the
renderListpattern”. - Partial updates protect performance when data volumes grow.
- A pattern catalog sets the foundation for a design system where Figma components and code share the same names.
Practice
- Follow along: implement card, list, and banner patterns as render functions, feeding them the same state to compare outputs.
- Extend: build a helper like
createPatternRendererso every pattern follows the samerender+onMountlifecycle. - Debug: intentionally mutate state inside a render function, observe the bug, and revert to a pure function.
- Done when: at least three patterns accept the same state shape, one of them supports partial updates, and everything renders correctly.
Wrap-up
Reusable patterns speed up UI expansion. Next we will design API error states and network messaging on top of these patterns.
💬 댓글
이 글에 대한 의견을 남겨주세요