Show HN: Aberdeen – 반응형 UI를 위한 우아한 접근 방식
요약
Aberdeen은 순수 TypeScript를 기반으로 한 반응형 UI 라이브러리입니다. ES6 Proxy를 활용해 세밀한 속성 변경을 추적하며, 가상 DOM이나 컴파일러 없이도 효율적인 DOM 업데이트를 구현합니다.
핵심 포인트
- ES6 Proxy를 이용한 세밀한 상태 추적 및 자동 DOM 업데이트
- 가상 DOM 디핑(diffing)이나 컴파일러 매직이 필요 없는 가벼운 구조
- TypeScript 기반의 직관적이고 배우기 쉬운 API 제공
- 양방향 데이터 바인딩 및 효율적인 리스트 렌더링 지원
순수 TypeScript를 사용한 반응형 UI (Reactive UIs). 배우기 쉽고, 빠르게 배포할 수 있습니다.
Aberdeen은 세밀한 속성 접근 추적 (fine-grained property access tracking)을 위해 상태를 ES6 Proxy 객체로 감싼 다음, 변경된 데이터에 의존하는 DOM 구축 클로저 (closures)만을 자동으로 재실행합니다. 따라서 가상 DOM 디핑 (virtual DOM diffing)이나 컴파일러 매직 (compiler magic) 없이도 정밀한 DOM 업데이트를 구현할 수 있습니다.
먼저, 필수적인 반응형 카운터 예제로 시작해 보겠습니다. 만약 공식 웹사이트에서 이 글을 읽고 있다면, 코드 아래에서 작동하는 데모를 볼 수 있으며, 코드 오른쪽 상단에 직접 테스트해 볼 수 있는 'edit' 버튼이 있을 것입니다.
import A from 'aberdeen';
// 상태를 프록시된 (observable) 객체로 정의합니다.
// '$' 접두사는 반응형 객체를 눈에 띄게 만들기 위한 관례일 뿐입니다.
const $state = A.proxy({question: "How many roads must a man walk down?", answer: 42});
A('h3', () => {
// 이 함수는 question이나 answer가 변경될 때마다 다시 실행됩니다.
A('text=', ${$state.question} ↪ ${$state.answer || 'Blowing in the wind'})
});
// $state.question을 <input>에 양방향 바인딩 (Two-way bind) 합니다.
A('input placeholder=Question bind=', A.ref($state, 'question'))
// <input>과 버튼 모두를 사용하여 $state.answer를 수정할 수 있도록 합니다.
A('div.row margin-top:1em', () => {
A('button text=- click=', () => $state.answer--);
A('input type=number bind=', A.ref($state, 'answer'))
A('button text=+ click=', () => $state.answer++);
});
자, 다음은 조금 더 복잡한 앱인 다음과 같은 동작을 가진 할 일 목록 (todo-list)입니다:
휴.. 이제 코드를 살펴보겠습니다:
import A from "aberdeen";
import {grow, shrink} from "aberdeen/transitions";
// 데이터를 저장하기 위해 간단한 클래스를 사용하겠습니다.
class TodoItem {
constructor(public label: string = '', public done: boolean = false) {}
toggle() { this.done = !this.done; }
}
// 최상위 사용자 인터페이스 (user interface).
function drawMain() {
// 초기 아이템들을 추가합니다. 여기에 A.proxy()를 적용하겠습니다!
let $items: TodoItem[] = A.proxy([
new TodoItem('Make todo-list demo', true),
new TodoItem('Learn Aberdeen', false),
]);
// 라벨(label) 순으로 정렬된 리스트를 그립니다.
A.onEach($items, drawItem, $item => $item.label);
// 아이템 추가 및 완료된 항목 삭제 버튼을 추가합니다.
A('div.row', () => {
A('button text=+ click=', () => $items.push(new TodoItem("")));
A('button.outline text="Delete checked" click=', () => {
for(let idx in $items) {
if ($items[idx].done) delete $items[idx];
}
});
});
};
// 각 할 일(todo) 리스트 아이템마다 호출됩니다.
function drawItem($item) {
// 라벨이 없는 아이템은 편집(editing) 상태로 열립니다.
// 아래에서 생성될 div.row 스코프 외부에서 이 A.proxy를 생성하여,
// 해당 상태가 재실행될 때도 유지되도록 합니다.
let $editing: {value: boolean} = A.proxy($item.label == '');
A('div.row', todoItemStyle, 'create=', grow, 'destroy=', shrink, () => {
// item.done에 따라 div.row에 조건부로 클래스를 추가합니다.
A({".done": A.ref($item,'done')});
// 체크표시는 CSS를 사용하여 숨겨집니다.
A('div.checkmark text=✅');
if ($editing.value) {
// 편집 중인 동안 라벨을 유지하기 위한 프록시된(Proxied) 문자열입니다.
const $labelCopy = A.proxy($item.label);
function save() {
$editing.value = false;
$item.label = $labelCopy.value;
}
// 라벨 <input>. Enter 키 또는 버튼을 사용하여 저장합니다.
A('input placeholder=Label bind=', $labelCopy, 'keydown=', e => e.key==='Enter' && save());
A('button.outline text=Cancel click=', () => $editing.value = false);
A('button text=Save click=', save);
} else {
// 텍스트로서의 라벨.
A('p text=', $item.label);
// 완료되지 않은 경우 편집 아이콘을 표시합니다.
if (!$item.done) {
A('a text=Edit click=', e => {
$editing.value = true;
$e.stopPropagation(); // 상태 토글(toggle)도 함께 일어나는 것을 방지합니다.
});
}
// 행(row)을 클릭하면 완료 상태를 토글합니다.
A('cursor:pointer click=', () => $item.done = !$item.done);
}
});
}
// 이 데모에 특화된 컴포넌트 로컬 CSS를 삽입합니다.
const todoItemStyle = A.insertCss({
"&": "mb:0.5rem",
".checkmark": "opacity:0.2",
"&.done": "text-decoration:line-through",
"&.done .checkmark": "opacity:1"
});
// 시작!
drawMain();
추가 예시:
물론 위의 예시들을 살펴보는 것도 좋을 것입니다!
만약 Claude Code, GitHub Copilot 또는 Skills를 지원하는 다른 AI 에이전트 (AI agents)를 사용한다면, Aberdeen에는 라이브러리를 효과적으로 사용하는 방법에 대해 AI에게 전문적인 지식을 제공하는 skill/ 디렉토리가 포함되어 있습니다.
이를 사용하려면, 해당 skill을 프로젝트의 .claude/skills 디렉토리로 심볼릭 링크 (symlink)를 거는 것을 권장합니다:
mkdir -p .claude/skills
ln -s ../../node_modules/aberdeen/skill .claude/skills/aberdeen
변경 사항의 전체 이력은 CHANGELOG.md를 참조하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 HN Claude Code Search의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기