Javascript

자바스크립트의 Proxy 객체 톺아보기

Proxy 객체는 어떤 역할을 하고 어디에 활용할 수 있을까?

2025년 12월 06일
Reading Time : 7

✨ 들어가며

  • 자주 즐겨보는 유튜브인 코딩애플에서 오랜만에(?) 코딩 채널다운 영상을 올려주셨다.
  • Proxy 객체로 구현하는 반응성 프로그래밍에 대한 내용이었는데, 흥미가 생겨서 직접 활용해보고 공부한 것들을 정리해보려고 한다.

🕵️‍♂️ Proxy 객체란?

  • Proxy 객체는 ES6에서 도입된 기능으로, 다른 객체에 대한 접근을 가로채 사이드 이펙트를 추가하거나 감시하는 역할을 수행한다.

  • Proxy는 두 가지 주요 요소로 구성된다.

    • 타겟 객체(Target Object): 감시하거나 조작하려는 실제 객체
    • 핸들러(Handler) 혹은 트랩(Trap): 타겟 객체에 대한 작업을 정의하는 메서드들의 집합 (set, get 등)
js
// 선언
const target = {
  message: "Hello, Proxy!",
};
const handler = {
  // set을 정의하여 속성 할당을 가로챔
  set: (obj, prop, value) => {
    // obj는 target, prop은 접근하려는 속성 이름, value는 할당하려는 값
    obj[prop] = value;
    return true; // 할당이 성공했음을 나타냄
  },
  // get을 정의하여 속성 접근을 가로챔
  get: (obj, prop) => {
    return obj[prop];
  },
};
const proxy = new Proxy(target, handler);

💻 Proxy 맛보기

  • 위에서 말했듯이 Proxy 객체는 다양한 활용이 가능하다. 몇 가지 예시를 통해 살펴보자

1. 데이터 변경 감지

  • 객체의 속성 변경을 감지하고, 변경 시 특정 동작을 수행할 수 있다.
  • 아래 예제는 set 트랩을 활용해 속성이 변경된 경우 콘솔에 출력하는 예제이다.
js
const target = {
  message: "Hello, Proxy!",
};
 
const handler = {
  set: (obj, prop, value) => {
    console.log(`속성 ${prop}${value}(으)로 변경되었습니다.`);
    obj[prop] = value;
    return true;
  },
  get: (obj, prop) => {
    return obj[prop];
  },
};
const proxy = new Proxy(target, handler);
 
proxy.message = "Bye, Proxy!"; // 콘솔: 속성 message가 "Bye, Proxy!"(으)로 변경되었습니다.

2. 유효성 검사

  • 상태 값을 읽기 전에 특정 비즈니스 로직을 적용하거나, 잘못된 데이터 접근을 방지할 수 있다.
  • 아래 예제는 get 트랩을 활용해 나이 속성에 number 타입을 제외한 타입이 할당될 경우, 안전한 기본값을 제공하는 예제이다.
js
const target = {
  age: "나이",
};
 
const handler = {
  // set 생략..
  get: (obj, prop) => {
    if (prop === "age" && typeof obj[prop] !== "number") {
      console.warn(`경고: age 속성은 number 타입이어야 합니다. 기본값 0을 반환합니다.`);
      return 0; // 안전한 기본값 반환
    }
    return obj[prop];
  },
};
const proxy = new Proxy(target, handler);

이런 방식은 더 다양한 로직에 활용이 가능하다.

💻 Proxy 활용하기

  • 살펴본 활용법을 바탕으로, 간단한 양방향 바인딩 예제를 구현해보자.
  • 아래 예제는 HTML input 요소와 Proxy 객체를 활용해, 사용자가 입력한 값이 실시간으로 화면에 반영되는 기능을 제공한다.
html
<input type="text" id="input" />
<span id="output"></span>
 
<script>
  const input = document.getElementById("input");
  const output = document.getElementById("output");
 
  // html 요소 업데이트
  const updateUI = (value) => {
    input.value = value;
    output.textContent = value;
  };
 
  const proxy = new Proxy(
    {
      text: "첫 렌더링",
    },
    {
      set(target, prop, value) {
        const result = Reflect.set(target, prop, value);
        if (prop === "text") {
          updateUI(value);
        }
        return result;
      },
    }
  );
 
  updateUI(proxy.text); // 초기값 설정
 
  input.addEventListener("input", (event) => {
    proxy.text = event.target.value;
  });
</script>

오랜만에 html 문법 사용하니 상당히 어색하다.. 😂

💻 Proxy로 심플한 리액트 구현하기

  • 지금까지의 맛보기와 활용하기 예제를 바탕으로, 리렌더링 베이스의 기본 동작원리를 가지고 있는 구현체를 만들어보았다.
  • 리액트와 같이 렌더링을 위한 root 노드를 만든다.
html
<div id="root"></div>
  • 상태를 감지하고 렌더링을 트리거할 기본 상태 관리 코드를 작성한다.
js
const createReactiveState = (initialState, renderCallback) => {
  const handler = {
    set(target, prop, value) {
      const result = Reflect.set(target, prop, value);
 
      // 상태가 변경되면, 연결된 렌더링 콜백을 실행 (반응성)
      renderCallback();
 
      return result;
    },
  };
  return new Proxy(initialState, handler);
};
  • 상태 관리 코드를 사용한 커스텀 Counter Component를 작성한다.
js
function CounterComponent(targetElement) {
  // 상태를 Proxy로 감싸고, 렌더링 함수를 콜백으로 전달
  const state = createReactiveState({ count: 0 }, render);
 
  // 렌더링 함수 (UI를 DOM에 반영)
  function render() {
    targetElement.innerHTML = `
      <h1>Counter: ${state.count}</h1>
      <button id="incrementBtn">증가</button>
      <button id="decrementBtn">감소</button>
    `;
 
    // 렌더링 후 이벤트 리스너를 다시 연결해야 함 (이 부분이 리액트와 다름 - Virtual DOM이 없음)
    document.getElementById("incrementBtn").onclick = () => {
      // 상태 변경 -> Proxy set 트랩 발동 -> render() 자동 호출
      state.count++;
    };
    document.getElementById("decrementBtn").onclick = () => {
      state.count--;
    };
  }
 
  // 초기 렌더링
  render();
}
 
const appRoot = document.getElementById("root");
if (appRoot) {
  CounterComponent(appRoot);
}
  • Proxy 객체를 활용해 리액트의 useState와 유사한 상태 관리 및 렌더링 메커니즘을 구현했다.
Proxy기반 심플 리액트 카운터

Proxy기반 심플 리액트 카운터

📝 결론

💡Proxy 객체는 자바스크립트에서 객체의 동작을 세밀하게 제어할 수 있는 강력한 도구이다.
 
해당 객체를 사용해봄으로써 리액트 상태값에 의존하지 않으면서 반응형 프로그래밍을 구현하는 방법을 이해할 수 있었다.