Dependency Tracking 기본 (I)
Source: Dev.to
(번역할 텍스트가 제공되지 않았습니다. 번역이 필요한 본문을 알려주시면 한국어로 번역해 드리겠습니다.)
의존성 추적이란?
Dependency Tracking은 데이터 조각들 간의 관계를 자동으로 수집하고 기록하는 기술입니다. 이는 기본 데이터가 변경될 때마다 시스템이 정확히 재계산이나 부수 효과를 트리거하도록 하여, 세밀한 반응성을 위한 기반이 됩니다.
간단한 비유로 Excel을 들 수 있습니다: 셀을 변경하면 그 셀에 의존하는 모든 다른 셀들이 자동으로 다시 계산됩니다. 이것이 바로 의존성 추적의 핵심 아이디어입니다.
Source: …
의존성 추적의 세 가지 핵심 역할
의존성 추적을 기반으로 하는 반응형 시스템은 일반적으로 세 가지 구성 요소로 이루어집니다:
- Source (Signal) – 기본적인 가변 값으로, 가장 작은 상태 단위입니다.
- Computed (Derived Value) – Source에서 파생된 순수 함수이며, 보통 캐시되고 지연 평가됩니다.
- Effect (Side Effect) – 외부 세계와 상호 작용하는 작업(예: DOM 업데이트, 데이터 가져오기, 로깅 등)입니다.
이들은 명확한 의존성 그래프를 형성합니다:

Source:
의존성 추적 작동 방식
Dependency Tracking은 일반적으로 세 단계로 구현됩니다:
1. 추적 (의존성 수집)
계산된 값이나 effect를 실행할 때, 시스템은 getter를 사용해 어떤 소스가 접근되었는지 기록합니다. 활성화된 계산은 스택에 저장되며, 이는 의존성을 수집하는 핵심 메커니즘 역할을 합니다.
export interface Computation {
dependencies: Set<any>;
execute: () => void;
}
const effectStack: Computation[] = [];
export function subscribe(current: Computation, subscriptions: Set<any>): void {
subscriptions.add(current);
current.dependencies.add(subscriptions);
}
function createSignal<T>(value: T) {
const subscribers = new Set<any>();
const getter = () => {
const currEffect = effectStack[effectStack.length - 1];
if (currEffect) subscribe(currEffect, subscribers);
return value;
};
const setter = (newValue: T) => {
if (newValue === value) return;
value = newValue;
subscribers.forEach(sub => sub.execute());
};
return { getter, setter };
}
2. 알림 (의존성 재실행)
소스가 업데이트되면 시스템은 모든 의존 계산에 알림을 보냅니다. 정리(cleanup)가 중요합니다—정리가 없으면 오래된 조건에서 남은 의존성이 계속해서 실행될 수 있습니다.
function effect(fn: () => void) {
const runner: Computation = {
execute: () => {
cleanupDependencies(runner);
runWithStack(runner, fn);
},
dependencies: new Set<any>(),
};
runner.execute(); // 처음 한 번 실행
}
function cleanupDependencies(computation: Computation) {
computation.dependencies.forEach(subscription => {
subscription.delete(computation);
});
computation.dependencies.clear();
}
export function runWithStack<T>(computation: Computation, fn: () => T): T {
effectStack.push(computation);
try {
return fn();
} finally {
effectStack.pop();
}
}
3. 스케줄링 (배치 및 최적화)
스케줄러는 중복 실행을 방지하고 업데이트를 효율적으로 병합합니다.
function schedule(job) {
queueMicrotask(job);
}
다양한 프레임워크가 더 고급 스케줄링을 구현하지만, 이것이 기본 아이디어입니다.
Pull‑based vs. Push‑based Reactivity
| 유형 | 설명 | 예시 |
|---|---|---|
| Pull‑based | UI가 데이터 변경을 쿼리함 (예: diffing) | Virtual DOM (React) |
| Push‑based | 데이터가 의존자에게 업데이트를 푸시함 | Signals, MobX, Solid.js |
Signals 기반 시스템은 일반적으로 push‑based 업데이트를 채택하며, 이는 불필요한 재렌더링을 크게 줄이고 전역 diffing을 방지합니다.
동적 의존성 처리
의존성 추적의 까다로운 측면은 동적 의존성으로, 예를 들어:
effect(() => {
if (userId()) {
fetchProfile(userId());
}
});
조건이 변경되면 시스템은 다음을 수행해야 합니다:
- 이전 의존성 추적 중지
- 관련 있을 때만 새로운 의존성 추적
이는 대부분의 런타임에서 정리(cleanup)와 스택 기반 실행이 필수적인 이유입니다.
프레임워크 비교: 의존성 추적 구현 방식
| 프레임워크 | 메커니즘 | 스케줄러 |
|---|---|---|
| Solid.js | 런타임, 스택 기반 추적 | 마이크로태스크 + 배칭 |
| Vue 3 | 프록시 기반 런타임 추적 | 작업 큐 (매크로 태스크) |
| MobX | 전역 래핑된 getter | 마이크로태스크 |
| Svelte | 컴파일 타임 정적 분석 | 동기 또는 마이크로태스크 |
React의 의존성 모델은 매우 다르며 다음 기사에서 자세히 살펴볼 것입니다.
결론
Dependency Tracking은 세밀한 반응성을 위한 기본 아키텍처를 제공합니다. 데이터와 연산 사이의 관계를 자동으로 수집함으로써 시스템은 정확하고 효율적으로 업데이트할 수 있습니다. 의존성 추적을 마스터하면 더 나은 UI 아키텍처를 설계하고, 렌더링 워크플로를 최적화하며, 현대 반응형 프레임워크가 작동하는 방식을 이해하는 데 도움이 됩니다.
다음 기사: 의존성 추적 (II)
다음 장에서는 React의 의존성 모델을 분석하고, 그것이 세밀한 시스템과 어떻게 다른지, 그리고 Signals가 왜 더 깔끔한 솔루션을 제공하는지 살펴보겠습니다. 기대해 주세요!