JavaScript

[새로운 기능] Proxy

  • -

이번 포스트에서는 Vue.js 등 프론트엔드 프레임워크 들에서 반응성을 위해 사용하는 Proxy에 대해서 살펴보자.

 

반응성이란?

 

반응성(Reactivity)

반응성이란 선언적인 방식으로 어떤 값에 대한 변경에 대한 제어를 수행하는 프로그래밍 방식을 말한다.

예를 들어 다음과 같은 엑셀을 생각해보자.

A와 B에는 각각 10, 20이 할당되어 있다. Sum에는 이들의 합을 저장하도록 선언되어있으므로 30이 저장된다. 이 상황에서 A의 값을 20으로 변경하면 Sum은 자동으로 계산 결과를 수정해서 40으로 변경된다. 이것이 바로 반응성이다.

 

JavaScript의 반응성

하지만 우리의 일반적인 프로그램은 반응성을 갖지 못한다.

const nums = { A: 10, B: 20 };

let sum = nums.A + nums.B;
console.log(sum); // 30

nums.A = 20;
console.log(sum); // 여전히 30

어떻게 하면 sum이 반응성을 갖게 할 것인가? 여기에 대한 답이 Proxy이다.

 

Proxy란..

Proxy란 대리, 대신의 뜻으로 메서드의 기본적인 동작을 가로채서 추가적인 작업을 수행하거나 대체하는 행위/객체를 말한다. (스프링의 AOP처럼 여기저기서 Proxy가 많이 사용되고 있다.)

고객은 음식을 먹기 위해서 음식점에 직접 연락을 취할 수 있다. 추가로 배달 업체를 통해서도 주문을 진행할 수 있는데 배달 업체를 통하면 놀랍게도 집까지 음식이 배달된다!

고객의 입장에서는 두 경우 모두 음식을 먹을 수 있기 때문에 주문하는 행위는 동일한데 배달업체는 그 주문을 가로채서 배달이라는 행위를 끼워 넣는 것이다. 이때 배달 업체가 바로 Proxy가 된다.

 

Proxy 활용

Proxy - JavaScript | MDN (mozilla.org)

 

Proxy - JavaScript | MDN

The Proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object.

developer.mozilla.org

 

Proxy 생성

proxy는 원본 데이터인 target과 proxy의 동작을 규정하는 handler로 구성된다.

const proxy = new Proxy(target, handler);

간단히 proxy를 생성해서 사용해 보자.

const target = {
  foodA: "김치찌개",
};
const handler = {}
// proxy 생성
const proxy = new Proxy(target, handler);

console.log(`주문: ${target.foodA},`, target);
console.log(`주문: ${proxy.foodA}, `, proxy);

foodA를 갖는 target과 handler 객체를 이용해서 proxy를 생성하는 코드이다.  여기서는 handler에 특별한 내용은 설정하지 않았다. 

그리고 target과 proxy를 통해서 주문을 진행해 보았는데 실행결과를 살펴보면 특별한 차이는 보이지 않는다. 두 경우 모두 김치찌개가 잘 주문되었다. 이럴 거면 Proxy를 사용한 의미가 없는데..

 

Handler와 Trap

proxy가 proxy 답게 동작을 가로채려면 handler의 개입이 필요하다. handler는 trap들을 가지는 placeholder 객체인데 여기서 trap이라는 용어가 중요하다. trap이란 target 객체의 property에 접근하기 위한 set/get 등 메서드로  이미 정의가 되어있다.

JavaScript에서는 console.log(target.food)와 같이 어떤 객체의 속성을 사용하면 property 값이 바로 반환되는 것이 아니라 [[Get]]이라는 내부 메서드가 호출된다. target.food='자장면'처럼 값을 변경할 때에도 [[Set]]이라는 내부 메서드가 호출된다.

 

Trap 중 get메서드는 target의 [[Get]]메서드에 대응하는 메서드이다. get Trap은  console.log(proxy.foodA)와 같이 접근했을 때 호출되며 target의 [[Get]]을 호출해 준다. 따라서 이 Trap 들을 사용하면 실제 target의 메서드가 호출되기 전에 끼어들기가 가능한 것이다.

다음은 내부 메서드와 매핑되는 handler의 trap 들이다.

내부 메서드 Trap method 내부 메서드 Trap method 내부 메서드 Trap method
[[Get]] get [[Set]] set [[OwnPropertyKeys]] ownKeys
[[Construct]] construct [[Delete]] deleteProperty [[GetOwnProperty]] getOwnPropertyDescriptor
[[HasProperty]] Has [[GetPrototypeOf]] gegtPrototypeOf [[DefineOwnProperty]] defineProperty
[[IsExtensible]] isExtensible [[Call]] apply [[SetPrototypeOf]] setPrototypeOf

 

자주 사용되는 trap 메서드

그럼 자주 사용되는 trap 메서드인 get과 set에 대해 살펴보자.

get

get은 target의 [[Get]]이 호출될 때 즉 속성을 사용할 때 proxy에서 호출되는 trap 메서드이다. handler에서 이 메서드를 미 구현하면 바로 [[Get]]이 호출된다.

다음은 get trap의 기본 syntax이다.

new Proxy(target, {
  get: function(target, property, receiver) {}
});
  • target: target 객체에 대한 참조
  • property: 사용하려는 target의 속성 이름
  • receiver: proxy 자신 또는 proxy를 상속받은 객체
  • return: 속성에 해당하는 값(Any)

간단한 사용 예를 살펴보자.

const target = {
  foodA: "김치찌개",
}

const handler = {
  get(target, prop) {
    console.log("get 개입 성공 - 부가적인 동작 처리 가능");
    return target[prop] + " 집까지 배달됩니다.~";
  }
}

const proxy = new Proxy(target, handler);

console.log(`주문: ${target.foodA},`, target);
console.log(`주문: ${proxy.foodA}, `, proxy);

get trap에서는 요청에 개입하였음을 표시하기 위한 로그와 함께 고객이 주문한 음식에 배달이 추가되었다.

따라서 target을 통한 주문에는 그냥 김치찌개가 끝이지만 proxy를 통하면 배달을 받을 수 있게 되었다.

 

set

set trap은 target의 [[Set]]이 호출될 때 즉 속성을 수정하거나 추가할 때 동작하는 trap이다. handler에서 이 trap을 미구현하면 바로 target의 [[Set]]이 호출된다.

다음은 set trap의 syntax이다.

new Proxy(target, {
  set: function(target, property, value, receiver) {
}});
  • target: target 객체에 대한 참조
  • property: 사용하려는 target의 속성 이름
  • value: target의 속성에 할당하려는 값
  • receiver: proxy 자신 또는 proxy를 상속받은 객체

set trap의 적용 예를 살펴보자. 이제 handler에 다음과 같이 set trap을 작성한다.

const handler = {
	. . .,
    set(target, property, value) {
        console.log("set 개입 성공 - 부가적인 동작 처리 가능");
        target[property] = value;
    },
};

 

이제 주문할 음식을 김치찌개에서 삼겹살로 변경해 보자.

proxy.foodA = "삼겹살";
console.log(`주문: ${target.foodA},`, target);
console.log(`주문: ${proxy.foodA}, `, proxy);

동작 결과를 살펴보면 성공적으로 set 과정에 개입하여 부가적인 동작을 처리할 수 있음을 알 수 있다. 배달도 당연하다.

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.