Vue에서는 데이터(state)를 변경하면, 변경사항이 자동으로 DOM에 업데이트 된다. 그런데, 주의할 점은 데이터가 변경되자마자 바로 DOM에 반영되는 것은 아니다.
<template>
<div>
<button v-if="isMounted" ref="target">성공!</button>
</div>
</template>
<script>
export default {
name: "getCurrentTime",
data() {
return {
isMounted: false,
};
},
mounted() {
this.isMounted = true;
console.log(this.$refs.target);
},
};
</script>
위의 코드는 컴포넌트가 마운트되면, isMounted
라는 데이터를 true
로 만들고 "성공!" 이란 버튼을 보여준다. 분명 mounted
훅 내부에서isMounted
를 true
로 만든 이후, DOM 요소에 접근하여 콘솔에 찍어봤는데 undefined
가 출력된다. 왜 이런 것일까?
Vue는 DOM 업데이트를 비동기로 처리한다. 데이터 변경이 일어나게 되면 데이터에 대한 변경사항을 큐(Queue)에 넣는다. 그리고 다음 이벤트 루프(tick)를 통해 큐가 비워질때(flushes) DOM 업데이트가 일어난다.
위는 Vue 공식 문서의 내용을 한국어로 옮긴 내용인데, 저것만 봐서는 통 무슨 말인지 모르겠다. 위를 이해하기 위해서는 먼저 자바스크립트의 이벤트 루프에 대해 공부 할 필요가 있다. 이벤트 루프 하나만 다루어도 내용이 많아, 해당 내용은 다른 포스트에서 따로 다루도록 하겠다. 그래도 $nextTick
을 이해할 수 있을 정도로만 이벤트 루프에 대해 살펴보자.
console.log("첫번째");
setTimeout(function() {
console.log("두번째");
}, 0);
console.log("세번째");
위와 같은 코드가 있다. 참고로, setTimeout
은 대표적인 비동기 함수로, 지연시간 (단위: ms) 이후에 첫번째 인자로 받은 콜백 함수를 실행하는 함수이다. 그런데, 즉시 실행을 원해 0 밀리초 뒤에 실행하도록 했다. 과연 위 코드를 실행하면 어떤 결과가 나올까?
놀랍게도 분명 0초 뒤에 실행하라고 했음에도, 세번째
가 출력된 이후에 두번째
가 출력되었다. 이런 결과가 나온 이유를 이해하기 위해서 자바스크립트가 내부적으로 어떻게 동작하는지 잠깐 살펴보자.
자바스크립트는 모든 코드를 순차적으로 하나씩 실행을 하고나면, 이벤트 루프라는 것을 돌게 된다.
이벤트 루프란 콜백 큐(Callback Queue)에 있는 콜백을 꺼내어 실행하는 로직이다.
그럼 콜백 큐란 무엇일까? 콜백 큐는 말 그대로 콜백으로 이루어진 큐로 콜백을 관리하는 저장소 같은 곳이다. 콜백 큐에는 어떤 콜백들이 들어가는 것일까? 위에서 보았던 setTimeout
과 같은 비동기적으로 실행되는 함수의 콜백함수가 들어간다. 그리고 이벤트 루프는 콜백 큐에 저장된 콜백을 하나씩 꺼내어 실행한다. 그럼 다시 위에서 보았던 코드를 살펴보자.
위와 같이 1번, 2번, 3번 코드가 순차적으로 하나씩 실행된다. 이벤트 루프는 아직 실행되기 전이니 콜백 큐는 비어있다. 1번 코드는 동기적으로 잘 실행되어 1번째
를 출력하고, 이제 2번 코드가 실행 될 차례이다.
2번 코드가 실행될때 setTimeout
이 비동기 함수임을 안 자바스크립트는 지연시간과 관계없이 일단 콜백을 어딘가에 등록만 해둔다. (몇 ms 뒤에 실행하는지는 현재 시점에서 판단하지 않는다.) 이렇게 등록이 완료되면, 3번 코드가 실행되고 3번 코드는 1번 코드와 마찬가지로 동기적으로 실행되어 3번째
를 출력한다.
이제 모든 코드가 실행되었으니 이벤트 루프가 실행 될 차례이다. 아까 setTimeout
함수를 실행할때 0ms 뒤에 콜백함수를 실행하도록 했다. 따라서 이벤트 루프는 실행 조건(0ms 이후)을 만족하는 콜백을 콜백 큐에 삽입하고 조금 이따 이벤트 루프는 콜백을 꺼내어 실행하게 된다. 이렇게 2번째
가 가장 마지막에 출력된다. 이렇게 이벤트 루프는 콜백들의 실행 조건이 충족되었는지 확인하고, 콜백을 실행하는 로직을 계속해서 반복해서 하게 된다. 그래서 이벤트 '루프'다.
다시 $nextTick
이야기로 돌아와보자. Vue에서 데이터를 수정하게 되면 DOM에 바로 업데이트 되는 것이 아니라 다음 이벤트 루프가 시작 될때까지 버퍼에 저장된다. (여기서 말하는 이벤트 루프가 위에서 보았던 그 이벤트 루프다.) 현재 이벤트 루프가 끝나고 다음 이벤트 루프가 시작되면 버퍼에 저장되었던 변경사항을 꺼내 DOM에 반영한다. 장황하게 설명하였지만, 결론은 "데이터를 변경해도 바로 DOM에 반영이 되지 않는다" 이다.
$nextTick
은 데이터 변경 후 DOM에 까지 업데이트가 완료된 후 인자로 전달받은 콜백함수를 실행한다. 다음과 같이 사용할 수 있다.
this.$nextTick(function() {
// DOM에 변경사항 모두 반영 후 실행
});
}
또는 awiat
/ async
와 함께 활용할 수도 있다.
async foo() {
await this.$nextTick();
// DOM에 변경사항 모두 반영 후 실행
}