본문 바로가기

개발이야기

순수 자바스크립트로 리액티브한 변수 만들기

리액트(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 같은 함수를 만들어 활용해보는 것도 좋은 방법이다!

728x90
반응형