Proxy
객체를 사용하면 한 객체에 대한 기본 작업을 가로채고 재정의하는 프록시를 만들 수 있다.
프록시 객체를 사용하면 속성 가져오기, 설정 및 정의와 같은 기본 객체 작업을 재정의 할 수 있다. 프록시 객체는 일반적으로 속성 액세스를 기록하고, 입력의 유효성을 검사하고, 형식을 지정하거나, 삭제하는 데 사용된다.
즉, 프록시 객체는 대상 객체를 트랩을 통해 기본 명령을 재정의해서 사용한다.
대상 객체인 target 객체를 만들고, new Proxy()
로 프록시 객체를 생성한다.
const target = {
message1: "hello",
message2: "everyone"
};
const handler1 = {};
const proxy1 = new Proxy(target, handler1);
프록시 객체인 proxy1
의 모양은 다음과 같다.
Proxy {}
> [[Handler]]: Object
> [[Target]]: Object
> [[IsRevoked]]: false
핸들러에서 아무런 작업을 하지 않기 때문에 원래 대상처럼 작동한다.
console.log(proxy1.message1); // hello
console.log(proxy1.message2); // everyone
프록시를 커스텀하기 위해서 handler를 정의한다. (트랩 함수 추가)
const target = {
message1: "hello",
message2: "everyone"
};
const handler2 = {
get(target, prop, receiver) {
return "world";
}
};
const proxy2 = new Proxy(target, handler2);
handler에서 get하는 동시에 world
를 return하게 했으므로 기존 값이 아닌 world
가 출력된다.
console.log(proxy2.message1); // world
console.log(proxy2.message2); // world
Reflect 를 이용해 다음과 같이 재정의할 수 있다.
const target = {
message1: "hello",
message2: "everyone"
};
const handler3 = {
get(target, prop, receiver) {
if (prop === "message2") {
return "world";
}
return Reflect.get(...arguments); // target[propertyKey] 값을 반환, target[prop] 값이 undefined가 아닌 TypeError 발생
},
};
const proxy3 = new Proxy(target, handler3);
console.log(proxy3.message1); // hello
console.log(proxy3.message2); // world
Reflect 🤔 ?
하나의 네임스페이스에 모인 API로Object
보다 더 깔끔하게 구현 가능하다.
ESLint Rule Preset 사용 시 no-prototype-builtins 규칙으로 인해obj.hasOwnProperty
사용을 못하고Object.prototype.hasOwnProperty.call
를 사용해야하는데,Relect
를 사용한다면 깔끔하게 코드 작성 가능하다.
Object.prototype.hasOwnProperty.call(obj, 'prop');
->Reflect.has(obj, 'prop');
Composition API - 반응형 데이터 에서 'reactive는 프록시 객체를 반환한다.' 에서의 프록시 객체가 이때까지 살펴봤던 프록시 객체이다.
@vue > reactivity.cjs.js 파일에서 reactive와 ref 코드를 살펴보자.
createReactiveObject
에서 살펴볼 수 있듯이 new Proxy
로 프록시 객체를 반환하는 것을 볼 수 있다.
function reactive(target) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target;
}
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
if (!shared.isObject(target)) {
{
console.warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (target["__v_raw" /* ReactiveFlags.RAW */] &&
!(isReadonly && target["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */])) {
return target;
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
// only specific value types can be observed.
const targetType = getTargetType(target);
if (targetType === 0 /* TargetType.INVALID */) {
return target;
}
const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);
proxyMap.set(target, proxy);
return proxy;
}
간단히 pseudo-code 로 나타내면 다음과 같다.
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key)
return target[key]
},
set(target, key, value) {
target[key] = value
trigger(target, key)
}
})
}
reactive
와 다르게 프록시 객체를 반환하지 않고 getter/setter
를 사용하는 것을 볼 수 있다.
function ref(value) {
return createRef(value, false);
}
function createRef(rawValue, shallow) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
class RefImpl {
constructor(value, __v_isShallow) {
this.__v_isShallow = __v_isShallow;
this.dep = undefined;
this.__v_isRef = true;
this._rawValue = __v_isShallow ? value : toRaw(value);
this._value = __v_isShallow ? value : toReactive(value);
}
get value() {
trackRefValue(this);
return this._value;
}
set value(newVal) {
const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
newVal = useDirectValue ? newVal : toRaw(newVal);
if (shared.hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = useDirectValue ? newVal : toReactive(newVal);
triggerRefValue(this, newVal);
}
}
}
간단히 pseudo-code 로 나타내면 다음과 같다.
function ref(value) {
const refObject = {
get value() {
track(refObject, 'value')
return value
},
set value(newValue) {
value = newValue
trigger(refObject, 'value')
}
}
return refObject
}