BaseHandlers
- Handler는 reactive 내에서 생성되는 Proxy의 핸들러 인수(객체; instanace)를 의미
- 즉, 각 (객체; instance) 메서드는 Proxy Trap 이다.
- 해당 모듈에서 가장 중요한 함수는 createGetter, createSetter 두 함수이다.
사전에 필요한 기술적 지식
- Vue3는 중첩된 객체에도 반응성을 제공한다.
- But!! Proxy를 사용하기 때문에, 대상 객체 내부에 외부 객체가 존재할 경우, 동등 비교 시 문제가 발생한다.
const obj = {};
const arr = reactive([obj]);
const reactiveObj = arr[0];
obj !== reactiveObj
- Vue3는 내부적으로 Proxy와의 동등비교를 기존 객체와의 비교로 변경한다.
- 반응성 참조 객체와 실제 객체가 별도로 존재할 수 있다는 것에서 발생하는 문제
- hasOwnProperty를 사용하여 항상 Proxy가 기존 객체를 참조할 수 있도록 트랩 함수를 제공한다.
//js 네이티브 메서드 호출을 프록시
function hasOwnProperty(this:object, key: string) {
// @ts-ignore
const obj = toRaw(this);
track(obj, TrackOpTypes.HAS, key);
return obj.hasOwnProperty(key);
}
createGetter
- 중첩 객체들도 재귀적으로 반응성을 제공한다
- 반응성 제공은 트랩 메서드가 호출될 때에 lazy하게 일어난다.
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// Vue3 내부적으로 사용. trap이 호출되는 경우
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
return shallow
} else if (
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
// 배열일 경우 다른 방식으로 동작함
const targetIsArray = isArray(target)
// 읽기 전용이 아닐 때, 배열은 이전의 예외처리가 추가된 핸들러를 이용하도록 처리함
// hasOwnProperty의 경우도 예외처리
if (!isReadonly) {
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}
// 일단 Reflect를 통해 target의 메서드를 호출
const res = Reflect.get(target, key, receiver)
// ES6 심볼 관련 예외 처리, __proto__등. 추적하지 않음.
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
// 메서드 호출 결과 리턴
return res
}
// readOnly가 아닌 경우 해당 객체 추적
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// shallow인 경우 내부 반응성을 제공하지 않음. 메서드 호출 결과 리턴
if (shallow) {
return res
}
// 메서드 호출 결과가 반응성 객체인 경우
// 객체 내에 반응성 객체를 넣어 초기화 한 경우
// target이 배열이고 숫자 인덱싱하여 검색하는 경우 반응형 객체를 그대로 리턴함
// 그 외에는 value를 리턴함.
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value
}
// 호출 결과가 객체인 경우 재귀적으로 리액티브하게 해줌
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
createSetter
- set 메서드가 호출되었을때, 기존값을 신규값으로 대체
- oldValue가 readOnly인 객체인데, 해당 객체의 value에 참조가 아닌 값을 할당하려는 경우 실패…
- shallow가 아닐 경우(shallow면 처리 x)
- 신규 값이 ReadOnly가 아니면서 Shallow가 아닐 경우 row 값으로 변경
- target의 속성일 경우는 처리함
- target[key]가 객체일 경우, 해당 객체의 프록시가 처리하도록 위임
- set 메서드가 호출되었을 때, 적절한 effect를 트리거
- key가 없었는데 생겼을 경우(Add)
- 키가 바뀌었을 경우(Set)
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false
}
if (!shallow) {
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
728x90
반응형
'FrameWorks > Vue' 카테고리의 다른 글
Vue의 watchEffect 와 watch 파헤치기 (0) | 2024.06.12 |
---|---|
Vue의 ModelValue 파헤치기 (0) | 2024.06.12 |
Vue의 nextTick() 파헤치기 (0) | 2024.06.12 |
Vue의 ref 파헤치기 (0) | 2024.06.12 |
reactive( ) (0) | 2023.07.11 |
댓글