2022-02-08 TIL

Fact

  • 리액트 상태관리를 직접 만들기 위해 자바스크립트 proxy를 공부했다.
  • 짤봇미션 리펙토링

Feelings

  • 자바스크립트 proxy는 내가알던 네트워크 proxy와 비슷한 맥락이라는 느낌이 들었다. 자바스크립트 proxy는 객체를 감싸 프로퍼티 읽기, 쓰기와 같은 객체에 가해지는 작업을 중간에서 가로채는 객체를 말한다.

Findings

  • 자바스크립트 Proxy는 특정 객체를 감싸 프로퍼티 읽기, 쓰기와 같은 객체에 가해지는 작업을 중간에서 가로채는 객체로, 가로채진 작업은 Proxy 자체에서 처리되기도 하고, 원래 객체가 처리하도록 그대로 전달되기도 합니다.

proxy는 다양한 라이브러리와 몇몇 브라우저 프레임워크에서 사용되고 있습니다.

let proxy = new Proxy(target, handler)
  • target - 감싸게 될 객체로, 함수를 포함한 모든 객체가 가능합니다.
  • handler - 동작을 가로채는 메서드인 ‘트랩(trap)’ 이 담긴 객체로, 여기서 proxy를 설정합니다(예시: get 트랩은 target의 프로퍼티를 읽을 때, set 트랩은 target의 프로퍼티를 쓸 때 활성화됨).

proxy에 작업이 가해지고, handler에 작업과 상응하는 트랩이 있으면 트랩이 실행되어 프락시가 이 작업을 처리할 기회를 얻게 됩니다. 트랩이 없으면 target에 작업이 직접 수행됩니다.

먼저, 트랩이 없는 프락시를 사용한 예시를 살펴봅시다.

let target = {}
let proxy = new Proxy(target, {}) // 빈 핸들러

proxy.test = 5
alert(target.test) // 5

alert(proxy, test) // 5

for (let key in proxy) alert(key)
test

위 예시의 프락시엔 트랩이 없기 때문에 proxy에 가해지는 모든 작업은 target에 전달됩니다.

  1. proxy.test =를 이용해 값을 쓰면 target에 새로운 값이 설정됩니다.
  2. proxy.test를 이용해 값을 읽으면 target에서 값을 읽어옵니다.
  3. proxy를 대상으로 반복 작업을 하면 target에 저장된 값이 반환됩니다.

Proxy는 일반 객체와는 다른 행동의 양상을 보이는 “특수 객체(exotic object)“입니다. 프로퍼티가 없고, handler가 비어있으면 Proxy에 가해지는 작업은 target에 곧바로 전달됩니다.

프락시의 트랩은 내부 메서드의 호출을 가로챕니다. 메서드 리스트는 https://ko.javascript.info/proxy에서 확인이 가능합니다.

규칙

내부 메서드나 트랩을 쓸 땐 자바스크립트에서 정한 몇 가지 규칙을 따라야한다.

  • 값을 쓰거나 지우는 게 성공적으로 처리되었으면 [[Set]], [[Delete]]는 반드시 true를 반환해야 합니다. 그렇지 않은 경우는 false를 반환해야 합니다.
  • proxy 객체를 대상으로 [[GetPrototype]]가 적용되면 proxy 객체의 타깃 객체에 [[GetPrototype]]를 적용한 것과 동일한 값이 반환되어야 합니다. 프락시의 프로토타입을 읽는 것은 타깃 객체의 프로토타입을 읽는 것과 동일해야 합니다.

get 트랩으로 프로퍼티 기본값 설정하기

  • target - 동작을 전달할 객체로 new Prox의 첫 번째 인자입니다.
  • property - 프로퍼티의 이름
  • receiver - 타깃 프로퍼티가 getter라면 receiver는 getter가 호출될 때 this 입니다. proxy 객체 자신이 this가 됩ㄴ디ㅏ. 프락시 객체를 상속받은 객체가 있다면 해당 객체가 this가 되기도 합니다.
let numbers = [0, 1, 2]

numbers = new Proxy(numbers, {
  get(target, prop) {
    if (prop in target) {
      return target[prop]
    } else {
      return 0
    }
  },
})

alert(number[1])
alert(number[123])

주의할 점

dictionary = new Proxy(dictionary, ...);

타깃 객체의 위치와 상관없이 proxy 객체는 타깃 객체를 덮어써야만 합니다. 객체를 proxy로 감싼 이후엔 절대로 타깃 객체를 참조하는 코드가 없어야합니다.

set 트랩으로 프로퍼티 값 검증하기

  • target - 동작을 전달할 객체로 new Proxy의 첫 번째 인자입니다.
  • property - 프로퍼티 이름
  • value - 프로퍼티 값
  • receiver - get 트랩과 유사하게 동작하는 객체로,setter 프로퍼티에만 관여합니다.

set트랩을 사용해 배열에 추가하려는 값이 숫자형인지 검증하기

let numbers = []

numbers = new Proxy(numbers, {
  set(target, prop, val) {
    if (typeof val == 'number') {
      target[prop] = val
      return true
    } else {
      return false
    }
  },
})

numbers.push(1)
numbers.push(2)
alert('length is: ' + numbers.length) //2

numbers.push('test') // Error: 'set' on proxy

alert('윗줄에서 에러가 발생했기 때문에 이 줄은 절대 실행되지 않습니다.')

proxy를 사용해도 기존에 있던 기능은 절대로 손상되지않습니다. push나 unshift 같이 배열에 값을 추가해주는 메서드들은 내부에서 [[Set]]를 사용하고 있기 때문에 메서드를 오버라이드 하지 않아도 프락시가 동작을 가로채고 값을 검증해줍니다.

Future Action

  • 빨리 프로젝트에 상태관리를 해주어야겠다..