FrameWorks/Vue

Vue의 nextTick() 파헤치기

ABCD 2024. 6. 12.

nextTick( )

export function nextTick<T = void>(
this: T,
fn?: (this: T) => void
): Promise<void> {
const p = currentFlushPromise || resolvedPromise
return fn ? p.then(this ? fn.bind(this) : fn) : p;
}

nextTick( )

  • 다음 DOM 업데이트 주기가 끝난 후 실행할 지연된 콜백
  • 데이터를 수정한 직후에 이 방법을 사용하여 업데이트 된 DOM을 가져온다.
const message = ref('Hello!')
const changeMessage = async newMessage => {
message.value = newMessage
// 여기서 DOM에서 얻은 값은 이전 값입니다.
await nextTick()
// nextTick이 업데이트된 값이 된 후 DOM 값을 가져옵니다.
console.log('Now DOM is updated')
}
const count = ref(0)
async function increment() {
count.value++
// DOM not yet updated
console.log(document.getElementById('counter').textContent) // 0
await nextTick()
// DOM is now updated
console.log(document.getElementById('counter').textContent) // 1
}
//위 코드와 아래 코드는 동일하게 작동한다.
const count = ref(0)
function increment() {
count.value++
// DOM not yet updated
console.log(document.getElementById('counter').textContent) // 0
// DOM is now updated
nextTick(()=>{
console.log(document.getElementById('counter').textContent) // 1
})
}

JS 실행 매커니즘

  • JS는 단일 쓰레드 언어이다. 즉, 한 번에 한 가지 작업만 수행할 수 있다.
    • 멀티 쓰레드는 동시에 여러작업을 수행할 수 있다.
  • 단일 쓰레드는 모든 작업이 대기열(큐)에 있어야 하며, 후속작업은 이전작업이 끝날 때 까지 기다려야 한다.
  • 이전 작업이 오래 걸리면 사용자 입 장에서 대기할 필요가 없는 일부 작업은 영원히 대기하게 된다.
    • 이 어이없는 문제를 해결하기 위해 JS에서 비동기라는 개념이 탄생했다.

개념

  • 기본 스레드에서 실행을 위해 대기 중인 작업을 동기화한다. 이전 작업 완료시 다음 작업을 실행한다.
  • 비동기 작업은 ‘태스크 큐’에 들어간다.
  • 태스크 큐가 메인 쓰레드에 비동기 태스크를 실행할 수 있음을 알리는 경우에만 메인 쓰레드에 들어간다.

동작 매커니즘

  • 모든 동기화 작업은 메인 쓰레드에서 실행되며, 실행 컨텍스트 스택을 형성한다.
  • 비동기 작업의 실행 결과가 있따면 해당 이벤트는 태스크 큐에 배치된다.
  • 실행 컨텍스트 스택의 모든 동기작업이 실행된 후 시스템은 태스크 큐에 어떤 이벤트가 있는지 확인한다.
    • 해당 비동기 작업은 대기 상태를 종료하고 실행 컨텍스트 스택에 들어가 실행을 시작한다.
  • 메인 쓰레드는 위 세 단계를 계속해서 반복한다.

Event Loop

nextTick 구현

  • 위의 개념을 기반으로 작업을 비동기화 하기 위한 수단이다.
const p = Promise.resolve();
export function nextTick(fn?: () => void): Promise<void> {
return fn ? p.then(fn) : p;
}

실행 순서는 어떻게 보장하지??

  • Vue3의 Scheduler 모듈은 작업 Queue와 콜백 Queue를 별도로 유지한다.
    • queueJob
      • 작업 큐잉
      • 작업 중복 제거
      • 태스크의 고유성을 보장하기 위해 각 작업 큐잉 시에 queueFlush호출
    • queuePostFlushCb
      • 콜백 큐잉
      • 콜백 중복 제거
      • 각 콜백 큐잉 시 callqueueFlush 실행
  • 해당 큐들을 별도로 유지하는 이유는 실행 시점 때문이다.
const queue: (Job | null)[] =[];
export function queueJob(job: Job) {
if(!queue.inclues(job)) {
queue.push(job);
queueFlush();
}
}
export function queuePostFlushCb(cb: Function | Function[]) {
if(!isArray(cb)) {
postFlushCbs.push(cb);
} else {
postFlushCbs.push(...cb)
}
queueFlush();
}

queueFlush( )

  • nextTick 함수를 통해 flushJob 함수를 비동기적으로 수행한다.
function queueFlush() {
if(!isFlushing && !isFlushPending) {
isFlushPending = true;
nextTick(flushJobs);
}
}

flushJob( )

  • 가장 먼저 작업 queue를 정렬 한 후 처리한다.

    • flush 전에 대기열을 정렬한다. 이 때 다음을 보장한다.

      • 컴포넌트는 부모에서 자식으로 업데이트된다.

        → 부모는 항상 자식보다 먼저 생성되므로 렌더 이펙트의 우선순위가 높기때문

      • 상위 컴포넌트 중 컴포넌트가 마운트 해제된 경우 업데이트를 skip할 수 있다.

        → 작업은 다른 flush된 작업이 실행되는 동안 무효화되기만 하며, flush시작 전 null이 될 수 없다.

  • 그 후 지연 콜백 큐인 postFlushCbs를 처리한다.

    • postFlushCbs 처리가 작업을 트리거 할 수 있다.
    • 재귀적으로 같은 작업을 반복한다.
function flushJobs(seen?: CountMap) {
isFlushPending = false
isFlushing = true
if (__DEV__) {
seen = seen || new Map()
}
queue.sort(comparator)
const check = __DEV__
? (job: SchedulerJob) => checkRecursiveUpdates(seen!, job)
: NOOP
try {
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
const job = queue[flushIndex]
if (job && job.active !== false) {
if (__DEV__ && check(job)) {
continue
}
// console.log(`running:`, job.id)
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
}
}
} finally {
flushIndex = 0
queue.length = 0
//postFlushCbs를 처리하는 곳
flushPostFlushCbs(seen)
isFlushing = false
currentFlushPromise = null
// some postFlushCb queued jobs!
// keep flushing until it drains.
if (queue.length || pendingPostFlushCbs.length) {
flushJobs(seen)
}
}
}
728x90
반응형

'FrameWorks > Vue' 카테고리의 다른 글

Vue의 watchEffect 와 watch 파헤치기  (0) 2024.06.12
Vue의 ModelValue 파헤치기  (0) 2024.06.12
Vue의 BaseHandler 파헤치기  (0) 2024.06.12
Vue의 ref 파헤치기  (0) 2024.06.12
reactive( )  (0) 2023.07.11

댓글