"형, 리액트에서는 input 태그를 쓸 때 onChange 파라미터로 event라는 놈을 받잖아요. 이 놈 정체가 뭐에요?"
"그거? 프록시 객체라고 찾아봐"
"프록시 그거 야동볼 때 쓰는거 아니.."

자주 들어왔던 용어기는 한데... 프록시가 대체 뭘까?! 프록시 객체는 또 무엇일까?

"혼란하다"

프록시(Proxy)

물론 대부분의 사람들은 프록시를 VPN과 더불어 한국에서는 접속하지 못하는 은밀한 사이트에 접근할 수 있도록 해주는 먹구름 속의 한 줄기 빛으로 생각하는 경우가 많다.

프록시를 내 맘대로 정의내리자면 '대신 해주는', '중개자", "대신 요청하고 대신 응답 받아주는" 정도가 맞는 말일 것 같다.

프록시 서버는 클라이언트와 서버 사이에서 데이터를 대신 요청하고 대신 받아주는 역할을 한다. 보안을 이유로 클라이언트가 서버에 직접 접근하지 못하게 하기 위해서, 혹은 중간에서 자주 쓰는 데이터를 캐싱해놓아 서버의 부담을 덜어주기 위해서 사용하기도 한다. (물론 프록시가 위치한 곳에 따라 더 많고 다양한 기능이 있겠지만)

프록시 패턴 (Proxy Pattern)

프록시 객체는 프록시 패턴을 사용할 때 실제 객체를 대신하는 객체를 말한다.

// 객체 하나 정의
function Users() { 
  this.users = ["레쉬", "철수", "영희"]
} 

// 유저 정보를 추가하는 기능
Users.prototype.add = function(name, callback) {
  let self = this;
  this.users.push(name);
} 
Users.prototype.remove = function(name, callback) {
  let self = this;
  this.users = this.users.filter(user => user !== name);
}

일단 위와 같이 Users 객체를 만들어보았다. 이 객체는 add(name) 할 때마다 name이 Users 객체에 추가되어 저장된다.
그런데 만약 여기 다른 기능을 추가하고 싶다면 어떻게 해야 할까? 그냥 객체를 뜯어고치는 방법도 있겠지만, 객체를 뜯어고치는 것은 이 객체를 사용하고 있는 다른 코드들에게 영향을 줘 사이드 이펙트를 일으킬 수 있기 때문에 프록시 객체를 만들어 기능을 추가해주는 것이 바람직하다.

그러면 이제 유저가 새롭게 추가되거나 삭제된 횟수를 보여주는 기능을 추가한 프록시 객체를 만들어보자.

function UsersProxy() {
  let users = new Users();
  let count = 0;
  return {
    add: function(name, callback) {
      users.add(name);
      count = count + 1;
    },
    remove: function(name, callback) {
      users.remove(name);
      count = count + 1;
    },
    getUsers: function() { 
      return users;
    },
    getCount: function() {
      return count;
    }
  }
}

기존에 프로토타입으로 선언해뒀던 add와 remove에 count 기능을 추가하여 오버라이딩(?)해준 모습이다. 프록시 객체에서 기존 객체의 메소드를 재정의하는 것 또한 오버라이드라고 명명하는지는 잘 모르겠으나, 방식 자체는 오버라이드와 비슷한 듯하다.

한가지 특이한 점을 꼽자면, 기존 Users 객체에서는 users로 접근하면 users list를 얻을 수 있었으나, UsersProxy 객체에서는 get 메소드를 작성해 가져왔다는 점이다.

리액트에서의 프록시 객체

그러면 리액트에서는 프록시 객체를 어떻게 활용했을까?
이 차이를 확인해보기 위해서 DOM과 ReactDOM에서 각각 이벤트리스너를 만들어 event parameter를 출력해봤다.

DOM

<!DOCTYPE HTML>
<html>
  <body>
    <input id="input_text"/>
  </body>
</html>

이 html 파일을 브라우저로 연 뒤 콘솔창에서 아래와 같이 작성해주면 된다.

document.getElementById('input_text').addEventListener('change', function(e) {
    console.log(e);
})

input창에 값을 입력하면, 콘솔 창에 다음과 같은 오브젝트가 나타난다.

image.png

ReactDOM

import React, { PureComponent } from 'react';

class Example extends PureComponent {
  blahblah (e) {
    console.log(e);
  }
  render() {
    return (
      <div> 
        <input onChange={this.blahblah}/>
      </div>
    )
  }
}

export default Example;

리액트 코드를 작성해준 뒤 input창에 값을 입력하면서 콘솔창을 확인해보도록 하자.

image.png

DOM에서는 event객체 이름이 Event였는데, ReactDOM에서는 SyntheticEvent이다. 즉, ReactDOM에서는 DOM의 객체를 모체 삼아 프록시객체를 만들어 사용함을 알 수 있었다!

실제로 리액트 공식 문서 SyntheticEvent에서는 SyntheticEvent를 Wrapper로 표현하고 있다.

Event객체에서는 변수로 선언되었었던 bubbles, cancelable 등을 SyntheticEvent에서는 get과 set으로 가져올 수 있게 된 것도 확인할 수 있었다.