Curated questions covering Composition API, reactivity, Pinia, Vue Router, directives, lifecycle hooks, Vuex, and Vue 3 features.
Vue.js is a progressive JavaScript framework for building user interfaces. Key features: reactive data binding, component-based architecture, Composition API (Vue 3), Options API, virtual DOM, Vue Router, Pinia state management, single-file components (.vue), and progressive adoption (can be used for a single widget or a full SPA).
// Options API\nexport default {\n data() { return { count: 0 }; },\n methods: { increment() { this.count++; } }\n};\n\n// Composition API\nconst count = ref(0);\nconst increment = () => count.value++;
setup() is the entry point for the Composition API. It runs before the component is created, before beforeCreate. It receives props and context as arguments. Variables and functions returned from setup() are available in the template.
export default {\n props: ["userId"],\n setup(props, { emit, attrs, slots }) {\n const user = ref(null);\n onMounted(() => fetchUser(props.userId).then(u => user.value = u));\n return { user };\n }\n};
const count = ref(0);\ncount.value++; // in JS\n// {{ count }} in template (no .value)\n\nconst state = reactive({ name: "Alice", age: 30 });\nstate.name = "Bob"; // direct mutation OK
const fullName = computed(() => `${firstName.value} ${lastName.value}`);\n\nwatch(userId, async (newId) => {\n user.value = await fetchUser(newId);\n});
Composables are functions that encapsulate and reuse stateful logic using the Composition API. They are the Vue 3 equivalent of React hooks. By convention, composable names start with "use".
// useFetch.js\nexport function useFetch(url) {\n const data = ref(null);\n const error = ref(null);\n const loading = ref(true);\n\n fetch(url)\n .then(r => r.json())\n .then(d => { data.value = d; loading.value = false; })\n .catch(e => { error.value = e; loading.value = false; });\n\n return { data, error, loading };\n}\n\n// Usage\nconst { data, loading } = useFetch("/api/users");
<script setup> is syntactic sugar for the Composition API. Variables, functions, and imports declared inside are automatically available in the template. No need to return anything. It is the recommended way to write Vue 3 components.
<script setup>\nimport { ref, computed } from "vue";\nimport UserCard from "./UserCard.vue";\n\nconst props = defineProps({ userId: Number });\nconst emit = defineEmits(["update"]);\n\nconst count = ref(0);\nconst doubled = computed(() => count.value * 2);\n</script>
<script setup>\nconst props = defineProps({\n title: { type: String, required: true },\n count: { type: Number, default: 0 }\n});\n\nconst emit = defineEmits(["update:count", "close"]);\nemit("update:count", props.count + 1);\n</script>
v-model creates two-way data binding. In Vue 3, v-model on a component is shorthand for :modelValue + @update:modelValue. Multiple v-model bindings are supported on a single component.
<!-- Parent -->\n<UserInput v-model="username" />\n<!-- Equivalent to: -->\n<UserInput :modelValue="username" @update:modelValue="username = $event" />\n\n<!-- Child component -->\nconst props = defineProps(["modelValue"]);\nconst emit = defineEmits(["update:modelValue"]);\nemit("update:modelValue", newValue);
Without key, Vue reuses DOM elements in place when the list changes, which can cause bugs with stateful elements (inputs, animations). With a unique key, Vue can track each element and correctly add/remove/reorder them.
<!-- Bad: index as key causes issues on reorder -->\n<li v-for="(item, i) in items" :key="i">{{ item.name }}</li>\n\n<!-- Good: stable unique ID -->\n<li v-for="item in items" :key="item.id">{{ item.name }}</li>
import { onMounted, onUnmounted } from "vue";\n\nonMounted(() => {\n window.addEventListener("resize", handler);\n});\nonUnmounted(() => {\n window.removeEventListener("resize", handler);\n});
Pinia is the official Vue state management library (replaces Vuex). Differences: no mutations (only state + actions), better TypeScript support, simpler API, modular by default, devtools support, and works with both Options and Composition API.
// stores/user.js\nexport const useUserStore = defineStore("user", () => {\n const user = ref(null);\n const isLoggedIn = computed(() => !!user.value);\n\n async function login(credentials) {\n user.value = await api.login(credentials);\n }\n\n return { user, isLoggedIn, login };\n});
Vue Router is the official routing library for Vue.js. Configure routes with createRouter() and createWebHistory().
const router = createRouter({\n history: createWebHistory(),\n routes: [\n { path: "/", component: Home },\n { path: "/users/:id", component: User },\n { path: "/admin", component: Admin, meta: { requiresAuth: true } },\n { path: "/:pathMatch(.*)*", component: NotFound }\n ]\n});
Navigation guards control route access. Types: global (beforeEach, afterEach), per-route (beforeEnter), and in-component (onBeforeRouteLeave, onBeforeRouteUpdate).
router.beforeEach((to, from) => {\n const auth = useAuthStore();\n if (to.meta.requiresAuth && !auth.isLoggedIn) {\n return { path: "/login", query: { redirect: to.fullPath } };\n }\n});
const route = useRoute();\nconst router = useRouter();\n\nconst userId = route.params.id;\nrouter.push({ name: "user", params: { id: 1 } });
Teleport renders a component's template in a different part of the DOM (outside the component tree) while keeping it logically inside the component. Used for modals, tooltips, and notifications that need to escape overflow/z-index constraints.
<template>\n <button @click="open = true">Open Modal</button>\n <Teleport to="body">\n <div v-if="open" class="modal">\n <p>Modal content</p>\n <button @click="open = false">Close</button>\n </div>\n </Teleport>\n</template>
Suspense handles async components and async setup() functions. It shows a fallback slot while waiting for async operations to complete. Works with async components (defineAsyncComponent) and components with async setup().
<Suspense>\n <template #default>\n <AsyncUserProfile /> <!-- has async setup() -->\n </template>\n <template #fallback>\n <LoadingSpinner />\n </template>\n</Suspense>
defineAsyncComponent lazily loads a component only when it is needed. Supports loading/error states and timeout configuration.
const UserProfile = defineAsyncComponent({\n loader: () => import("./UserProfile.vue"),\n loadingComponent: Spinner,\n errorComponent: ErrorDisplay,\n delay: 200,\n timeout: 3000\n});
provide/inject enables dependency injection across the component tree without prop drilling. A parent provides a value; any descendant can inject it. Use with readonly() to prevent mutation from children.
// Parent\nconst theme = ref("dark");\nprovide("theme", readonly(theme));\n\n// Any descendant\nconst theme = inject("theme");\n// inject("theme", "light") // with default value
// watchEffect - auto-tracks, runs immediately\nwatchEffect(() => {\n console.log(count.value, name.value); // tracks both\n});\n\n// watch - explicit, lazy\nwatch(count, (newVal, oldVal) => {\n console.log(newVal, oldVal);\n});
Custom directives add low-level DOM manipulation behavior to elements. Define with app.directive() globally or locally in a component. Hooks: created, beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted.
app.directive("focus", {\n mounted(el) { el.focus(); }\n});\n\n// Usage\n<input v-focus />
<input :value="name" @input="name = $event.target.value">\n<component v-bind="{ id: 1, class: 'active' }">
In script setup, component internals are private by default. defineExpose() explicitly exposes properties/methods to parent components accessing the component via template ref.
<script setup>\nconst count = ref(0);\nfunction reset() { count.value = 0; }\n\ndefineExpose({ count, reset }); // parent can access these\n</script>\n\n// Parent\nconst childRef = ref();\nchildRef.value.reset();
const state = reactive({ name: "Alice", age: 30 });\n\n// toRefs - safe destructuring\nconst { name, age } = toRefs(state);\nname.value = "Bob"; // still reactive
Plugins extend Vue functionality globally. A plugin is an object with an install() method or a function. Use app.use(plugin, options) to install. Common plugins: Vue Router, Pinia, i18n, custom UI libraries.
// Plugin definition\nconst myPlugin = {\n install(app, options) {\n app.config.globalProperties.$translate = (key) => options[key];\n app.component("MyComponent", MyComponent);\n app.directive("focus", focusDirective);\n }\n};\n\napp.use(myPlugin, { hello: "Bonjour" });
<input v-model.lazy="search" /> <!-- syncs on blur/enter -->\n<input v-model.number="age" /> <!-- converts to number -->\n<input v-model.trim="username" /> <!-- trims whitespace -->
KeepAlive caches component instances when they are toggled off, preserving their state and avoiding re-mounting. Use include/exclude to control which components are cached. Cached components trigger onActivated/onDeactivated hooks.
<KeepAlive :include="['UserList', 'Dashboard']" :max="10">\n <component :is="currentView" />\n</KeepAlive>
The Transition component applies enter/leave animations to a single element or component. Uses CSS classes (v-enter-from, v-enter-active, v-enter-to, v-leave-from, v-leave-active, v-leave-to) or JavaScript hooks.
<Transition name="fade">\n <p v-if="show">Hello</p>\n</Transition>\n\n<style>\n.fade-enter-active, .fade-leave-active { transition: opacity 0.3s; }\n.fade-enter-from, .fade-leave-to { opacity: 0; }\n</style>
<!-- Child: scoped slot -->\n<slot :item="item" :index="index" />\n\n<!-- Parent: receives slot data -->\n<template #default="{ item, index }">\n <span>{{ index }}: {{ item.name }}</span>\n</template>
useTemplateRef() (Vue 3.5+) is the modern way to get a reference to a template element or component. It replaces the pattern of declaring a ref with the same name as the template ref attribute.
<script setup>\nimport { useTemplateRef, onMounted } from "vue";\n\nconst inputEl = useTemplateRef("myInput");\nonMounted(() => inputEl.value.focus());\n</script>\n\n<template>\n <input ref="myInput" />\n</template>
// Global\napp.component("MyButton", MyButton);\n\n// Local (script setup - just import)\nimport MyButton from "./MyButton.vue";\n// MyButton is automatically available in template
Reactivity Transform was an experimental feature that allowed using reactive variables without .value (using $ref, $computed macros). It was removed in Vue 3.4 due to confusion about when .value is needed. The standard ref() with .value is the recommended approach.
Validated emits use defineEmits with an object syntax to validate event payloads at runtime. Unvalidated emits just declare event names as strings. Validation helps catch bugs during development.
const emit = defineEmits({\n // No validation\n click: null,\n // With validation\n submit: (payload) => {\n if (!payload.email) {\n console.warn("submit event missing email");\n return false;\n }\n return true;\n }\n});
Nuxt.js is a meta-framework built on Vue that adds: file-based routing, SSR/SSG/hybrid rendering, auto-imports (no need to import ref, computed, etc.), server API routes, layouts, middleware, and optimized production builds. It is to Vue what Next.js is to React.
v-memo memoizes a sub-tree of the template. It skips re-rendering the element and its children when the specified dependency array has not changed. Useful for optimizing large v-for lists.
<div v-for="item in list" :key="item.id" v-memo="[item.id, item.selected]">\n <!-- Only re-renders when item.id or item.selected changes -->\n <p>{{ item.name }}</p>\n <ExpensiveComponent :data="item" />\n</div>
app.config.errorHandler = (err, instance, info) => {\n Sentry.captureException(err);\n console.error(err);\n};
useAttrs() returns non-prop attributes passed to the component (class, style, event listeners not declared as props). useSlots() returns the slots object. Both are available in script setup without importing.
<script setup>\nconst attrs = useAttrs(); // { class, style, onCustomEvent }\nconst slots = useSlots(); // { default, header }\n\n// Disable automatic attribute inheritance\ndefineOptions({ inheritAttrs: false });\n</script>
import { mount, shallowMount } from "@vue/test-utils";\n\nconst wrapper = mount(UserCard, {\n props: { user: { name: "Alice" } }\n});\nexpect(wrapper.text()).toContain("Alice");
<script setup>\nconst inputEl = ref(null); // template ref\nonMounted(() => inputEl.value.focus());\n</script>\n<template>\n <input ref="inputEl" />\n</template>
defineModel() (Vue 3.4+) simplifies implementing v-model on a component. It creates a two-way binding automatically, replacing the manual defineProps + defineEmits pattern for v-model.
<script setup>\nconst model = defineModel(); // replaces modelValue prop + update:modelValue emit\n</script>\n<template>\n <input :value="model" @input="model = $event.target.value" />\n</template>\n\n<!-- Parent -->\n<MyInput v-model="username" />
onServerPrefetch(async () => {\n // runs on server only\n data.value = await fetchData();\n});\n\nonMounted(() => {\n // runs on client only\n initThirdPartyLib();\n});
// Legacy (globalProperties)\napp.config.globalProperties.$http = axios;\n\n// Modern (provide/inject)\napp.provide("http", axios);\n// In component:\nconst http = inject("http");
<TransitionGroup name="list" tag="ul">\n <li v-for="item in items" :key="item.id">{{ item.name }}</li>\n</TransitionGroup>\n\n<style>\n.list-enter-active, .list-leave-active { transition: all 0.3s; }\n.list-enter-from, .list-leave-to { opacity: 0; transform: translateX(30px); }\n.list-move { transition: transform 0.3s; }\n</style>
Explore 500+ free tutorials across 20+ languages and frameworks.