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에서 비동기라는 개념이 탄생했다.
개념
- 기본 스레드에서 실행을 위해 대기 중인 작업을 동기화한다. 이전 작업 완료시 다음 작업을 실행한다.
- 비동기 작업은 ‘태스크 큐’에 들어간다.
- “태스크 큐”가 메인 쓰레드에 비동기 태스크를 실행할 수 있음을 알리는 경우에만 메인 쓰레드에 들어간다.
동작 매커니즘
- 모든 동기화 작업은 메인 쓰레드에서 실행되며, ”실행 컨텍스트 스택”을 형성한다.
- 비동기 작업의 실행 결과가 있따면 해당 이벤트는 “태스크 큐”에 배치된다.
- “실행 컨텍스트 스택”의 모든 동기작업이 실행된 후 시스템은 “태스크 큐”에 어떤 이벤트가 있는지 확인한다.
- 해당 비동기 작업은 대기 상태를 종료하고 실행 컨텍스트 스택에 들어가 실행을 시작한다.
- 메인 쓰레드는 위 세 단계를 계속해서 반복한다.
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 실행
- queueJob
- 해당 큐들을 별도로 유지하는 이유는 실행 시점 때문이다.
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 |
댓글