본문 바로가기

FrontEnd

[Vue3] Vue3은 리렌더링을 어떻게 트리거할까?

반응형

Vue3의 공식문서를 읽어봐도 reactivity의 종속성 추적에 대한 설명만 있지

결과적으로 리렌더링을 어떻게 트리거 하는지에 대한 설명은 없는것 같다.

 

에반 유가 설명한 Vue3 코어 모듈(https://itchallenger.tistory.com/807)의 역할에 기반해서

해당 코드가 어떻게 동작할지 추론한 후 코드베이스를 분석하였다.

Reactivity 모듈에 의해 render 함수가 트리거되어 새로운 VDOM을 만든다.

정답 : Renderer 모듈의 setupRenderEffect

https://github.com/vuejs/core/blob/main/packages/runtime-core/src/renderer.ts#L1288

 

GitHub - vuejs/core: 🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web. - GitHub - vuejs/core: 🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework fo...

github.com

해당 함수는 1540 라인에서 아래와 같이 리렌더링(update)를 유발하는 함수(componentUpdateFn)를 세팅하고

1563 라인에서 해당 함수를 실행한다.

    // create reactive effect for rendering
    const effect = (instance.effect = new ReactiveEffect(
      componentUpdateFn,
      () => queueJob(update),
      instance.scope // track it in component's effect scope
    ))

    const update: SchedulerJob = (instance.update = () => effect.run())
    
    
    // ...
    update()

2022.12.10 - [Vue.js] - Vue3의 반응성 완벽 이해 1편 : 종속성 추적 에서 설명한 바를 바탕으로 정리하면,

  1. 컴포넌트 mount시 setupRenderEffect가 실행된다.
  2. update()를 실행하면, componentUpdateFn이 activeEffect에 등록된 상태로 내부의 patch 함수를 실행한다.
  3. VDOM의 조정을 진행하면서 props의 속성을 조회하고, 이때 props의 의존성으로 componentUpdateFn 함수가 등록된다.
  4. props의 변경은 trigger를 호출하고 해당 컴포넌트의 리렌더링을 발생시키게 된다.

이 과정은 재귀적으로 발생한다. 컴포넌트 렌더링의 진입점이 patch 함수이기 때문이다.(L2314)

동작 원리를 간단하게 구현한 mini-vue

위의 설명을 위해 evan you가 준비한 예제이다.

소스코드를 쭉 읽어보면 위에서 설명한 내용과 거의 유사한 원리로 동작한다.

  1.  mountApp의 watchEffect에 의해 props 조회 시 렌더링 함수가 의존성(구독자) 로직의 일부가 된다
    • activeEffect가 렌더링 함수
  2. 버튼 클릭 시 trigger 함수가 호출되고, 이 때문에 watchEffect 내부 함수가 다시 실행된다.

실제 Vue3 코드베이스에는 watch에 해당하는 doWatch라는 함수가 별도가 존재한다.

실제로 Vue3의 렌더링은 마이크로태스크큐에서 실행되며, lazy 하기 때문에,

큐잉하고, sync를 사용하여 리렌더링의 트리거를 막고, patch 전후에 정해진 로직을 실행하는 등

다양하고 복잡한 로직이 들어가 있다.


심화 : Vue3는 렌더링을 어떻게 단 한번만 수행할까?

뷰는 내부적으로 렌더링을 pendingPostFlushCbs큐를 이용해 배치 처리한다 (scheduler.ts)

또한 flushJobs(scheduler.ts) 함수를 이용해

렌더링 이펙트를 인스턴스 id (컴포넌트 렌더링 순서)를 이용해 정렬하여 부모부터 렌더링 이펙트를 처리한다.

  • 데이터는 prop과 같은 반응형 객체 형태로 인스턴스에 존재하기 때문에, vdom과 데이터 처리를 분리하여 생각할 수 있다.
  • 리액트는 컴포넌트 렌더링이 뷰를 위한 데이터를 만드는 구조이기 때문에 부모부터 자식까지의 리렌더링을 전부 수행해야만 실제 뷰를 위한 데이터가 뭔지 알 수 있다

flushJobs에서 먼저 실행된 렌더링 job은 patch(renderer.ts)를 수행하면서 shouldUpdateComponent를 호출한다.

(componentRenderUtils.ts)

이 먼저 실행된 렌더링 job은 부모 job을 의미한다.

 

부모 job은 다음과 같은 논리를 통해 자식 컴포넌트 업데이트 작업을 여러번 처리하지 않도록 한다.

  • 부모의 rendering job에서 prop이 변경되지 않은 경우
    • 자식을 patch 할 필요가 없으며
    • 만약 자식이 prop이 그대로인 경우에도 리렌더링이 필요하면 이미 큐에 들어가 있다
  • 부모의 rendering job에서 prop이 변경된 경우
    • 부모는 자식의 인스턴스를 통해 해당 job이 뭔지 알 수 있다.
    • 해당 job을 큐에서 제거하고 실행하는 것으로 부모에서의 patch process를 자식의 patch process와 결합한다

 

 

 

 

 

 

반응형