Tags: vue, vue3, composition-api, reactivity, pinia, frontend, javascript, typescript
Level: intermediate
TL;DR: Vue 3 core architecture, Composition API reactivity mechanics, component lifecycle, Pinia state management, and production interview essentials.
-
Declarative component-driven framework utilizing a virtual DOM and compiler-informed optimizations.
-
Vue 3 reactivity relies on ES6 Proxies via reactive() and ref().
-
Composition API organizes logic by feature using setup functions rather than Options API options.
-
ref wraps primitive and object values in an object exposing a .value property.
-
reactive creates deep reactive proxies strictly for objects, collections, and arrays.
-
computed produces cached, read-only or writable reactive derivations updated solely on dependency invalidation.
-
watch observes explicit reactive sources lazily; watchEffect tracks dependencies immediately and automatically.
-
Template compiler statically analyzes ASTs to implement patch flags and block trees for fast diffing.
-
Provide and Inject enable dependency inversion across deep component hierarchies without prop drilling.
-
Built-in primitives include <Teleport>, <Suspense>, <KeepAlive>, and <Transition>.
-
Q: How does reactivity in Vue 3 differ fundamentally from Vue 2?R: Vue 2 used Object.defineProperty with getter/setter traps, which missed property additions, deletions, and direct array index updates. Vue 3 uses ES6 Proxy to intercept all operations natively, including property deletions and dynamic key assignments.
-
Q: When should you choose ref() over reactive()?R: I use ref() as the default primitive because it accepts primitives and objects, avoids reactivity loss during destructuring when using toRefs(), and provides clear tracking via .value. I reserve reactive() for tightly coupled state models.
-
Q: What is the difference between watch and watchEffect?R: watch is lazy, tracks only explicitly defined data sources, and provides both previous and current values. watchEffect runs immediately, tracks every reactive dependency accessed inside its execution synchronously, but does not provide the prior value.
-
Q: How does the script setup syntactic sugar optimize runtime and DX?R: It compiles directly into the component's setup() function, exposes top-level bindings directly to the template without manual returns, supports pure TypeScript type-based macro definitions like defineProps, and reduces runtime overhead.
-
Q: What is the difference between v-if and v-show?R: v-if conditionally mounts or unmounts elements from the DOM with lazy evaluation. v-show always renders the element into the DOM and toggles visibility via the CSS display property.
-
Q: Why did Pinia replace Vuex as the official recommendation?R: Pinia removes mutations for synchronous/asynchronous actions, supports complete TypeScript type inference without complex boilerplate, eliminates nested module namespaces, and is lighter and tree-shakeable.
-
Q: How do you handle deep component communication without prop drilling?R: I use provide() at the ancestor level and inject() in descendant components with InjectionKey<T> for strong typing, or state stores like Pinia for global application concerns.
-
Q: What causes loss of reactivity when destructuring a reactive object?R: ES6 destructuring unpacks values by reference or value into local variables, breaking the Proxy getter interception. Wrapping the object with toRefs() or using toRef() maintains connection via individual refs.
-
Destructuring properties directly from a reactive object without toRefs().
-
Accessing .value unnecessarily inside the <template> block where unwrapping is automatic.
-
Omitting .value when accessing or mutating ref instances inside JavaScript/TypeScript logic.
-
Mutating props directly inside a child component instead of emitting events to the parent.
-
Reassigning an entire reactive() object variable instead of updating properties or using Object.assign().
-
Using async callbacks inside watchEffect after initial synchronous execution, missing dynamic dependencies.
-
Using index as the :key attribute in v-for lists that undergo reordering, filtering, or deletions.
-
Forgetting to clean up manual side effects or event listeners inside onUnmounted or onWatcherCleanup.
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue';
interface User {
id: number;
name: string;
}
const props = defineProps<{
initialCount?: number;
}>();
const emit = defineEmits<{
(e: 'update', count: number): void;
}>();
const count = ref<number>(props.initialCount ?? 0);
const user = ref<User | null>(null);
const doubleCount = computed<number>(() => count.value * 2);
function increment(): void {
count.value++;
emit('update', count.value);
}
watch(count, (newVal, oldVal) => {
if (newVal !== oldVal) {
document.title = `Count: ${newVal}`;
}
});
onMounted(async () => {
const res = await fetch('/api/user/me');
user.value = await res.json();
});
</script>
<template>
<div class="counter">
<p v-if="user">User: {{ user.name }}</p>
<button @click="increment">Count: {{ count }} (Double: {{ doubleCount }})</button>
</div>
</template>
TypeScript
```import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
export const useUserStore = defineStore('user', () => {
const profile = ref<{ id: string; name: string } | null>(null);
const isAuthenticated = computed(() => profile.value !== null);
async function fetchProfile(id: string): Promise<void> {
const res = await fetch(/api/users/${id});
profile.value = await res.json();
}
function logout(): void {
profile.value = null;
}
return { profile, isAuthenticated, fetchProfile, logout };
});
npm create vue@latest
npm install
npm run dev
npm run build
npm run test:unit
```
-
[ ] Reactivity transform history and why it was dropped in favor of explicit .value
-
[ ] Compiler macros implementation: defineProps, defineEmits, defineExpose, defineModel
-
[ ] Custom directives lifecycle hooks: created, beforeMount, mounted, updated, unmounted
-
[ ] Virtual DOM PatchFlags bitwise operations and Block Tree diffing optimizations
-
[ ] KeepAlive cache eviction strategies with include, exclude, and max
-
[ ] SSR hydration mismatch debugging and client-only component wrappers
-
[x] Composition API basics vs Options API
-
[x] Pinia setup stores vs options stores syntax
-
[x] Scoped styles and CSS Modules implementation in SFCs
-
Vue.js Official Documentation (vuejs.org)
-
Pinia Official Documentation (pinia.vuejs.org)
-
Vue Router Official Documentation (router.vuejs.org)
-
Vue RFCs Repository ([github.com/vuejs/rfcs](https://www.google.com/search?q=https%3A%2F%2Fgithub.com%2Fvuejs%2Frfcs))