✨ 들어가며
- 자주 즐겨보는 유튜브인 코딩애플에서 오랜만에(?) 코딩 채널다운 영상을 올려주셨다.
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 객체는 자바스크립트에서 객체의 동작을 세밀하게 제어할 수 있는 강력한 도구이다.
해당 객체를 사용해봄으로써 리액트 상태값에 의존하지 않으면서 반응형 프로그래밍을 구현하는 방법을 이해할 수 있었다.