리액트(React)의 useState나 솔리드(Solid)의 createSignal처럼 순수 자바스크립트에서 리액티브한 변수를 만들려면 어떤 방법이 있을까? 내부적으로 활용할 수 있는 주요 기술과 직접 구현해보는 방법을 정리해보았다.
1. Proxy를 활용한 반응형 변수
Proxy 객체를 활용하면 속성 접근과 변경을 감지하여 반응형 변수를 만들 수 있다.
function createReactive(initialValue) {
let listeners = new Set();
const state = new Proxy({ value: initialValue }, {
get(target, prop) {
if (prop === 'subscribe') {
return (callback) => listeners.add(callback);
}
return target[prop];
},
set(target, prop, newValue) {
if (prop === 'value') {
target[prop] = newValue;
listeners.forEach((callback) => callback(newValue));
}
return true;
}
});
return state;
}
장점: 여러 구독자를 추가할 수 있으며, 객체 속성 변경을 쉽게 감지할 수 있다. 단점: 배열의 push, pop 같은 메서드는 추가적인 핸들링이 필요하다.
2. Object.defineProperty를 활용한 반응형 변수
Vue 2에서 사용했던 방식으로 getter와 setter를 이용해 값을 추적할 수 있다.
function createReactive(initialValue) {
let value = initialValue;
let listeners = new Set();
const obj = {};
Object.defineProperty(obj, 'value', {
get() {
return value;
},
set(newValue) {
value = newValue;
listeners.forEach((callback) => callback(newValue));
}
});
obj.subscribe = (callback) => listeners.add(callback);
return obj;
}
장점: 간단한 구현이 가능하고, 특정 속성에만 반응형을 적용할 수 있다. 단점: 개별 속성마다 defineProperty를 적용해야 하므로 유지보수가 어렵다.
3. EventTarget을 활용한 이벤트 기반 상태 관리
브라우저의 EventTarget을 활용해 상태 변경 이벤트를 발생시키는 방식이다.
function createSignal(initialValue) {
const eventTarget = new EventTarget();
let value = initialValue;
return {
get value() {
return value;
},
set value(newValue) {
value = newValue;
eventTarget.dispatchEvent(new CustomEvent("change", { detail: newValue }));
},
subscribe(callback) {
eventTarget.addEventListener("change", (event) => callback(event.detail));
}
};
}
장점: 브라우저에서 최적화된 이벤트 시스템을 활용할 수 있다.
단점: 이벤트 리스너를 직접 관리해야 하므로 코드가 길어질 수 있다.
4. MutationObserver를 활용한 DOM 기반 반응형 변수
HTML 요소를 감시하는 MutationObserver를 활용하면 DOM의 변화를 감지하여 상태 변경을 추적할 수 있다.
function createReactive(initialValue) {
const span = document.createElement("span");
span.textContent = initialValue;
const observers = new Set();
const observer = new MutationObserver(() => {
observers.forEach((callback) => callback(span.textContent));
});
observer.observe(span, { childList: true });
return {
get value() {
return span.textContent;
},
set value(newValue) {
span.textContent = newValue;
},
subscribe(callback) {
observers.add(callback);
}
};
}
장점: 브라우저가 제공하는 최적화된 감지 기능을 사용할 수 있다.
단점: DOM을 활용해야 하므로 순수 자바스크립트 환경에서는 비효율적일 수 있다.
5. WeakMap을 활용한 반응형 시스템
WeakMap을 활용하여 객체별 상태를 추적하고 변경 시 반응형 동작을 추가할 수 있다.
const stateMap = new WeakMap();
function createReactive(initialValue) {
let obj = {};
stateMap.set(obj, initialValue);
const listeners = new Set();
return new Proxy(obj, {
get(target, prop) {
if (prop === 'value') return stateMap.get(target);
if (prop === 'subscribe') return (callback) => listeners.add(callback);
},
set(target, prop, newValue) {
if (prop === 'value') {
stateMap.set(target, newValue);
listeners.forEach((callback) => callback(newValue));
}
return true;
}
});
}
장점: WeakMap을 활용해 메모리 관리가 용이하다.
단점: 객체 속성 변경을 감지하기 어려울 수 있다.
🚀 결론
방법장점단점
Proxy | 객체 전반을 감지 가능, Vue 3 방식 | 배열 메서드 감지가 어려움 |
Object.defineProperty | 단순 구현, Vue 2 방식 | 개별 속성에만 적용 가능 |
EventTarget | 브라우저 최적화된 이벤트 시스템 활용 | 이벤트 리스너를 직접 관리해야 함 |
MutationObserver | DOM 변경을 감지하는 방식 | JS만으로 사용하기엔 비효율적 |
WeakMap | 메모리 관리가 좋음 | 객체 속성 감지가 어려움 |
추천
🔹 간단한 상태 관리 → Proxy 추천
🔹 Vue 2 스타일 → Object.defineProperty
🔹 이벤트 기반 방식 → EventTarget
🔹 DOM 기반 상태 관리 → MutationObserver
🔹 메모리 관리 중요 → WeakMap
순수 자바스크립트에서 리액티브한 상태를 만든다면 Proxy 기반 방식이 가장 유연하고 확장성이 좋다. 직접 createReactive 같은 함수를 만들어 활용해보는 것도 좋은 방법이다!
'개발이야기' 카테고리의 다른 글
웹팩보다 10배 빠른 Rspack, 도대체 뭐길래? (1) | 2025.03.07 |
---|---|
React 고급 기술 익히기 - 11편: React 서버 컴포넌트(Server Components) 완벽 정리 (0) | 2025.02.19 |
Redux vs MobX vs Jotai vs Zustand 비교 (동작 방식 & 원리 차이점) (0) | 2025.02.14 |
React의 renderToString 실행 여부 감지 방법 (0) | 2025.02.14 |
🚀 렌더링 최적화: Virtual DOM, Incremental DOM, Signal 비교 및 Angular의 변화 (0) | 2025.02.14 |