FrameWorks/Vue

Vue의 BaseHandler 파헤치기

ABCD 2024. 6. 12.

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

댓글

💲 추천 글