Vue Complete Guide | Vue 3, Composition API, Reactivity Internals, Compiler, Production
이 글의 핵심
Vue 3 pairs the Composition API with a Proxy-based reactivity system, Virtual DOM patching, and template compiler optimizations. This guide covers usage and how the pieces fit together under the hood.
Introduction
Vue is a progressive framework for building UIs with declarative rendering and reactive state. Vue 3 deepened TypeScript ergonomics through the Composition API, while the runtime combines Proxy-based reactivity, Virtual DOM diffing, and compile-time optimizations from the template compiler.
This article explains practical API usage and the internal model: how tracking and scheduling work, how VNodes are patched, how Composition API code maps to the runtime, what the compiler optimizes, and which production patterns keep apps fast and maintainable.
1. Vue 3 at a glance
- Composition API: Encapsulate state and logic in reusable functions (composables), using
setupor<script setup>. - Reactivity: Objects are wrapped with Proxy;
refwraps values in a reactive reference (.valuein script, auto-unwrap in templates). - Rendering: Templates compile to render functions that produce VNode trees; the runtime patches the DOM when state changes.
- Ecosystem: Vue Router, Pinia, and Vite are common companions.
Options API remains supported, but Composition API + <script setup> is the usual default for new applications.
2. ref, reactive, computed, watch
2.1 ref (single values)
ref creates a reactive reference. In <script>, use .value; in templates, Vue unwraps refs for ergonomics.
<script setup lang="ts">
import { ref } from 'vue';
const count = ref(0);
function increment() {
count.value++;
}
</script>
<template>
<p>Count: {{ count }}</p>
<button type="button" @click="increment">+1</button>
</template>
2.2 reactive (objects)
reactive makes an object deeply reactive. Destructuring plain properties can lose reactivity unless you use helpers like toRefs / storeToRefs—patterns that are well documented for a reason.
<script setup lang="ts">
import { reactive } from 'vue';
const state = reactive({
count: 0,
user: { name: 'Ada', email: '[email protected]' },
});
function increment() {
state.count++;
}
</script>
<template>
<p>{{ state.count }} — {{ state.user.name }}</p>
<button type="button" @click="increment">+1</button>
</template>
2.3 computed
Derived state with automatic dependency tracking and caching. Recomputes only when dependencies change.
<script setup lang="ts">
import { ref, computed } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
const reversedName = computed(() =>
fullName.value.split('').reverse().join(''),
);
</script>
<template>
<input v-model="firstName" />
<input v-model="lastName" />
<p>{{ fullName }}</p>
<p>{{ reversedName }}</p>
</template>
2.4 watch and watchEffect
watch observes explicit sources; watchEffect auto-tracks reactive reads inside the callback.
<script setup lang="ts">
import { ref, watch, watchEffect } from 'vue';
const count = ref(0);
const message = ref('hello');
watch(count, (newVal, oldVal) => {
console.log(`count: ${oldVal} → ${newVal}`);
});
watch([count, message], ([c, m]) => {
console.log('both:', c, m);
});
watchEffect(() => {
console.log(`effect sees count=${count.value}`);
});
</script>
3. Composables and Pinia (overview)
Composables (useXxx) package reusable stateful logic. Pinia is the recommended store solution for Vue 3: defineStore with ref/computed defines stores that are still “just” reactive data consumed by components. See the dedicated Pinia guide for depth. Internally, remember: stores are downstream consumers of the same reactivity and effect system as components.
4. Internals: Proxy-based reactivity
Vue 3 uses Proxy and Reflect to intercept get/set on reactive objects. Compared to Vue 2’s Object.defineProperty approach, this model handles added/removed keys and many array operations more uniformly.
4.1 track and trigger (mental model)
- effect: A unit of work that reads reactive data (render effect,
watchEffectcallback,computedgetter, etc.). - track: While an effect runs, record which object keys were read as dependencies.
- trigger: When a key changes, schedule the effects that depend on it.
Render effects are often batched asynchronously, so multiple writes in the same tick may coalesce into fewer DOM updates.
4.2 Division of labor: ref vs reactive
reactive: Wraps objects with Proxy for deep reactivity.ref: Holds a value inside a wrapper so.valueaccess participates in the same dependency graph. Template auto-unwrap keeps authoring simple.
Variants like readonly, shallowReactive, and shallowRef adjust depth and writability without a different mental model.
4.3 Scheduling and a consistent UI
Batching means you cannot assume the DOM updates synchronously after every assignment. nextTick schedules code after the DOM has caught up—useful when measuring layout or moving focus. Conceptually, there is often one tick of delay between state and paint.
5. Virtual DOM and the patch algorithm
Vue compiles templates to render functions that return VNode trees. Comparing the previous and next tree and applying minimal DOM operations is patching.
5.1 What is a VNode?
A VNode is a lightweight description of a DOM node or component: tag, props, children, and optimization hints. Component VNodes carry definitions and slot information for constructing subtrees.
5.2 Patching and children reconciliation
If two VNodes at the same position differ in type, the runtime tends toward replace semantics (unmount/mount). If types match, it updates props, text, and children.
For lists, key identifies sibling identity. Keys enable correct moves and reuse when items reorder or insert. Vue 3’s keyed reconciliation uses dynamic programming and a longest increasing subsequence (LIS)-style strategy to minimize DOM movement. That is why using array index as key is risky: identity becomes unstable when the list mutates.
5.3 Block tree
The compiler groups static subtrees into blocks and tracks only dynamic children that may change. That avoids full-tree walks on every update in many cases and ties directly to compiler optimizations (next section).
6. Composition API: implementation angle
6.1 setup and the component instance
setup(props, ctx) runs before the render effect wires up, creating reactive state, computeds, and watchers. With Options API, returned bindings merge onto the instance; with <script setup>, the compiler generates equivalent bindings.
6.2 <script setup> compilation
<script setup> is compiled to plain setup code. Imports become registered components; macros like defineProps / defineEmits are compile-time to reduce runtime cost and boilerplate.
6.3 provide / inject
Parents provide values consumed by descendants via inject, walking the instance’s provider chain. To keep reactivity, pass ref/computed rather than raw snapshots—this matches how the reactivity graph propagates updates.
7. Compiler optimizations
The template compiler lowers runtime cost in several ways.
7.1 Static hoisting
Repeated static VNodes can be created once and reused; only dynamic branches need per-render work.
7.2 Patch flags
The compiler annotates nodes with bitflags describing what might change. At runtime, the patcher can skip full prop diffs and compare only the relevant fields.
7.3 v-once
v-once renders a subtree once and skips future updates—appropriate only when data truly never changes for that subtree.
8. Production patterns
8.1 Large data: shallowRef and markRaw
For huge lists or class instances that should not be deeply reactive, shallowRef or markRaw reduce tracking overhead. The goal is to keep reactivity only where UI actually depends on fine-grained changes.
8.2 v-memo
v-memo memoizes a subtree based on explicit dependencies—powerful but easy to misuse if dependencies are incomplete, leaving stale UI.
8.3 Async components and code splitting
defineAsyncComponent splits routes or heavy UI behind dynamic import boundaries. Pair with loading/error slots for better UX.
8.4 Suspense and errors
Suspense supplies fallback UI while async dependencies resolve. Complement with app.config.errorHandler (and equivalent) for centralized reporting.
8.5 Build checklist
- Ship production builds (drop dev warnings, verify tree-shaking).
- Split routes and heavy features; optimize assets.
- Watch Core Web Vitals; offload long work from the main thread when needed.
Summary
- Reactivity combines Proxy with effect scheduling: reads track, writes trigger.
- Rendering is VNode diffing (patch); lists need stable
keysemantics for efficient reconciliation. - Composition API improves reuse and typing;
<script setup>reduces boilerplate via compile-time transforms. - The compiler applies hoisting, patch flags, and block trees to shrink runtime work.
- Production apps layer shallow reactivity, memoization, code splitting, and error/suspense handling on top.
Keywords covered
Vue, Vue 3, Composition API, Reactivity, Proxy, Virtual DOM, Patch, Compiler, Pinia, TypeScript, production