FREE PREVIEW

You're viewing a free preview

This is a sample of 15 questions from our full collection of 48 interview questions.

Unlock all 48 questions with detailed explanations and code examples

Get Full Access

Basic Concepts

What is Vue.js and what are its primary purposes?

Vue.js is a progressive JavaScript framework for building user interfaces and single-page applications. It was created by Evan You and first released in 2014. Vue.js is designed to be incrementally adoptable, meaning you can integrate it into existing projects gradually or use it to build complete applications from scratch.

The primary purposes of Vue.js include:

  • User Interface Development: Creating reactive and dynamic user interfaces with declarative rendering
  • Component-Based Architecture: Building reusable, modular components that encapsulate functionality
  • State Management: Managing application state through reactive data binding
  • Single-Page Applications (SPAs): Developing fast, interactive web applications with client-side routing
  • Progressive Enhancement: Allowing developers to adopt Vue gradually in existing projects

Vue.js follows the Model-View-ViewModel (MVVM) pattern and emphasizes simplicity, flexibility, and performance.

References:

↑ Back to top

How do data binding and interpolation work in Vue?

Data binding in Vue.js creates a connection between the component's data and the DOM, allowing automatic updates when data changes. Vue uses a reactive system to track dependencies and update the DOM efficiently.

Text Interpolation

Vue uses double curly braces (mustaches) for text interpolation:

<template>
  <div>
    <p>{{ message }}</p>
    <p>{{ firstName + ' ' + lastName }}</p>
    <p>{{ number + 1 }}</p>
    <p>{{ ok ? 'YES' : 'NO' }}</p>
  </div>
</template>

Raw HTML

Use v-html directive for raw HTML content:

<div v-html="rawHtml"></div>

Attribute Binding

Use v-bind directive or shorthand : for attribute binding:

<div v-bind:id="dynamicId"></div>
<div :id="dynamicId"></div>
<button :disabled="isButtonDisabled">Click me</button>

Two-Way Data Binding

Use v-model for two-way data binding on form inputs:

<input v-model="message" placeholder="Edit me">
<p>Message is: {{ message }}</p>

Reactivity System

Vue's reactivity system automatically tracks dependencies and updates the DOM when data changes:

export default {
  data() {
    return {
      message: 'Hello Vue!',
      firstName: 'John',
      lastName: 'Doe'
    }
  }
}

References:

↑ Back to top

What is the role of directives in Vue, and can you give examples of common directives?

Directives in Vue.js are special attributes with the v- prefix that provide declarative functionality to templates. They apply reactive behavior to the DOM when the value of their expression changes.

Common Built-in Directives:

v-if, v-else-if, v-else

Conditional rendering:

<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>Not A/B</div>

v-show

Toggle element visibility:

<div v-show="isVisible">This may be hidden</div>

v-for

List rendering:

<ul>
  <li v-for="item in items" :key="item.id">
    {{ item.name }}
  </li>
</ul>

v-model

Two-way data binding:

<input v-model="message">
<select v-model="selected">
  <option value="a">A</option>
  <option value="b">B</option>
</select>

v-bind (:)

Attribute binding:

<img :src="imageSrc" :alt="imageAlt">
<div :class="{ active: isActive, 'text-danger': hasError }">

v-on (@)

Event handling:

<button @click="handleClick">Click me</button>
<input @keyup.enter="submit">

v-html

Raw HTML insertion:

<div v-html="rawHtmlContent"></div>

v-text

Text content:

<span v-text="message"></span>

v-once

One-time interpolation:

<div v-once>{{ message }}</div>

Custom Directives

You can also create custom directives:

app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})

References:

↑ Back to top

Component Fundamentals

What lifecycle hooks are available in Vue components and why are they useful?

Vue components have several lifecycle hooks that allow you to tap into different stages of a component's lifecycle.

Vue 2/3 Options API Lifecycle Hooks

export default {
  // Creation phase
  beforeCreate() {
    // Called before instance is created
    // Data and events not yet available
    console.log('beforeCreate')
  },
  
  created() {
    // Called after instance is created
    // Data, computed properties, methods available
    // DOM not yet available
    console.log('created')
    this.fetchData() // Good place for API calls
  },
  
  // Mounting phase
  beforeMount() {
    // Called before initial render
    // Template compiled but not yet rendered
    console.log('beforeMount')
  },
  
  mounted() {
    // Called after component is mounted to DOM
    // DOM is available
    console.log('mounted')
    this.$refs.input.focus() // DOM manipulation
  },
  
  // Updating phase
  beforeUpdate() {
    // Called before component re-renders
    console.log('beforeUpdate')
  },
  
  updated() {
    // Called after component re-renders
    console.log('updated')
    // Be careful of infinite loops here
  },
  
  // Destruction phase
  beforeUnmount() { // beforeDestroy in Vue 2
    // Called before component is unmounted
    console.log('beforeUnmount')
    // Cleanup: remove event listeners, clear timers
    clearInterval(this.timer)
  },
  
  unmounted() { // destroyed in Vue 2
    // Called after component is unmounted
    console.log('unmounted')
  }
}

Vue 3 Composition API Lifecycle Hooks

import { 
  onBeforeMount, 
  onMounted, 
  onBeforeUpdate, 
  onUpdated, 
  onBeforeUnmount, 
  onUnmounted 
} from 'vue'

export default {
  setup() {
    onBeforeMount(() => {
      console.log('beforeMount')
    })
    
    onMounted(() => {
      console.log('mounted')
      // DOM is available
    })
    
    onBeforeUpdate(() => {
      console.log('beforeUpdate')
    })
    
    onUpdated(() => {
      console.log('updated')
    })
    
    onBeforeUnmount(() => {
      console.log('beforeUnmount')
      // Cleanup
    })
    
    onUnmounted(() => {
      console.log('unmounted')
    })
  }
}

Common Use Cases

Data Fetching

// Options API
created() {
  this.fetchUserData()
},

// Composition API
onMounted(() => {
  fetchUserData()
})

DOM Manipulation

// Options API
mounted() {
  this.$refs.chart.initChart()
},

// Composition API
onMounted(() => {
  chartRef.value.initChart()
})

Cleanup

// Options API
beforeUnmount() {
  window.removeEventListener('scroll', this.handleScroll)
  clearInterval(this.intervalId)
},

// Composition API
onBeforeUnmount(() => {
  window.removeEventListener('scroll', handleScroll)
  clearInterval(intervalId.value)
})

Lifecycle Diagram

┌─────────────────┐
    │   beforeCreate  │
    └─────────────────┘
            │
    ┌─────────────────┐
    │     created     │ ← Good for API calls
    └─────────────────┘
            │
    ┌─────────────────┐
    │   beforeMount   │
    └─────────────────┘
            │
    ┌─────────────────┐
    │     mounted     │ ← DOM available
    └─────────────────┘
            │
    ┌─────────────────┐
    │  beforeUpdate   │ ← Data changed
    └─────────────────┘
            │
    ┌─────────────────┐
    │     updated     │ ← DOM updated
    └─────────────────┘
            │
    ┌─────────────────┐
    │ beforeUnmount   │ ← Cleanup
    └─────────────────┘
            │
    ┌─────────────────┐
    │    unmounted    │
    └─────────────────┘

References:

↑ Back to top

How can you communicate between parent and child components?

Vue provides several mechanisms for parent-child component communication, each serving different use cases.

1. Props (Parent → Child)

Basic Props

<!-- Parent Component -->
<template>
  <child-component 
    :message="parentMessage" 
    :user="currentUser"
    :is-active="true"
  />
</template>

<script>
export default {
  data() {
    return {
      parentMessage: 'Hello from parent',
      currentUser: { name: 'John', age: 30 }
    }
  }
}
</script>
<!-- Child Component -->
<template>
  <div>
    <p>{{ message }}</p>
    <p>User: {{ user.name }}</p>
  </div>
</template>

<script>
export default {
  props: {
    message: {
      type: String,
      required: true
    },
    user: {
      type: Object,
      default: () => ({})
    },
    isActive: Boolean
  }
}
</script>

2. Custom Events (Child → Parent)

Emitting Events

<!-- Child Component -->
<template>
  <button @click="handleClick">Click me</button>
</template>

<script>
export default {
  emits: ['button-clicked', 'update-value'],
  methods: {
    handleClick() {
      // Emit without data
      this.$emit('button-clicked')
      
      // Emit with data
      this.$emit('update-value', { id: 1, value: 'new value' })
    }
  }
}
</script>
<!-- Parent Component -->
<template>
  <child-component 
    @button-clicked="onButtonClicked"
    @update-value="onValueUpdate"
  />
</template>

<script>
export default {
  methods: {
    onButtonClicked() {
      console.log('Child button was clicked')
    },
    onValueUpdate(payload) {
      console.log('Value updated:', payload)
    }
  }
}
</script>

3. v-model (Two-way Binding)

Custom v-model Implementation

<!-- Child Component (Input Component) -->
<template>
  <input 
    :value="modelValue" 
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>
<!-- Parent Component -->
<template>
  <custom-input v-model="inputValue" />
  <p>Current value: {{ inputValue }}</p>
</template>

<script>
export default {
  data() {
    return {
      inputValue: 'initial value'
    }
  }
}
</script>

4. Template Refs (Parent → Child Direct Access)

<!-- Parent Component -->
<template>
  <child-component ref="childRef" />
  <button @click="callChildMethod">Call Child Method</button>
</template>

<script>
export default {
  methods: {
    callChildMethod() {
      // Direct access to child methods
      this.$refs.childRef.childMethod()
      
      // Access child data
      console.log(this.$refs.childRef.childData)
    }
  }
}
</script>
<!-- Child Component -->
<script>
export default {
  data() {
    return {
      childData: 'child data'
    }
  },
  methods: {
    childMethod() {
      console.log('Child method called')
    }
  }
}
</script>

5. Provide/Inject (Ancestor → Descendant)

<!-- Grandparent Component -->
<script>
export default {
  provide() {
    return {
      theme: 'dark',
      userPermissions: this.permissions
    }
  },
  data() {
    return {
      permissions: ['read', 'write']
    }
  }
}
</script>
<!-- Deep Child Component -->
<script>
export default {
  inject: ['theme', 'userPermissions'],
  created() {
    console.log(this.theme) // 'dark'
    console.log(this.userPermissions) // ['read', 'write']
  }
}
</script>

6. Composition API Communication

// Parent Component
import { ref, provide } from 'vue'

export default {
  setup() {
    const sharedData = ref('shared value')
    
    // Provide to descendants
    provide('sharedData', sharedData)
    
    const handleChildEvent = (data) => {
      console.log('Received from child:', data)
    }
    
    return {
      sharedData,
      handleChildEvent
    }
  }
}
// Child Component
import { inject, emit } from 'vue'

export default {
  emits: ['child-event'],
  setup(props, { emit }) {
    // Inject from ancestor
    const sharedData = inject('sharedData')
    
    const sendToParent = () => {
      emit('child-event', { message: 'Hello parent' })
    }
    
    return {
      sharedData,
      sendToParent
    }
  }
}

Communication Patterns Summary

Method Direction Use Case
Props Parent → Child Pass data down
Events Child → Parent Notify parent of changes
v-model Bidirectional Two-way data binding
Refs Parent → Child Direct method calls
Provide/Inject Ancestor → Descendant Deep component trees

References:

↑ Back to top

How can slots be used to create flexible components?

Slots are Vue's content distribution mechanism that allows you to create flexible, reusable components by letting parent components pass content into predefined locations within child components.

Basic Slot Usage

Component with Default Slot

<!-- BaseCard.vue -->
<template>
  <div class="card">
    <div class="card-header">
      <h3>Card Title</h3>
    </div>
    <div class="card-content">
      <!-- Default slot - content goes here -->
      <slot></slot>
    </div>
  </div>
</template>

Using the Component

<!-- Parent Component -->
<template>
  <base-card>
    <p>This content will be inserted into the slot</p>
    <button>Action Button</button>
  </base-card>
</template>

Named Slots

Component with Multiple Named Slots

<!-- BaseLayout.vue -->
<template>
  <div class="layout">
    <header class="header">
      <slot name="header"></slot>
    </header>
    
    <aside class="sidebar">
      <slot name="sidebar"></slot>
    </aside>
    
    <main class="main">
      <!-- Default slot -->
      <slot></slot>
    </main>
    
    <footer class="footer">
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

Using Named Slots

<!-- Parent Component -->
<template>
  <base-layout>
    <template #header>
      <h1>Page Title</h1>
      <nav>Navigation items</nav>
    </template>
    
    <template #sidebar>
      <ul>
        <li>Sidebar item 1</li>
        <li>Sidebar item 2</li>
      </ul>
    </template>
    
    <!-- Default slot content -->
    <p>This is the main content area</p>
    <article>Main article content</article>
    
    <template #footer>
      <p>&copy; 2024 My Company</p>
    </template>
  </base-layout>
</template>

Slot Fallback Content

Providing Default Content

<!-- BaseButton.vue -->
<template>
  <button :class="buttonClass" @click="$emit('click')">
    <slot>
      <!-- Fallback content if no slot content provided -->
      Click me
    </slot>
  </button>
</template>

<script>
export default {
  props: {
    variant: {
      type: String,
      default: 'primary'
    }
  },
  computed: {
    buttonClass() {
      return `btn btn-${this.variant}`
    }
  }
}
</script>

Usage with and without slot content

<template>
  <!-- Uses fallback content -->
  <base-button variant="primary" @click="handleClick" />
  
  <!-- Uses custom content -->
  <base-button variant="secondary" @click="handleSave">
    <icon name="save" /> Save Document
  </base-button>
</template>

Dynamic Slot Names

<!-- TabComponent.vue -->
<template>
  <div class="tabs">
    <div class="tab-headers">
      <button 
        v-for="tab in tabs" 
        :key="tab.name"
        @click="activeTab = tab.name"
        :class="{ active: activeTab === tab.name }"
      >
        {{ tab.label }}
      </button>
    </div>
    
    <div class="tab-content">
      <slot :name="activeTab"></slot>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    tabs: Array
  },
  data() {
    return {
      activeTab: this.tabs[0]?.name
    }
  }
}
</script>

Using Dynamic Slots

<template>
  <tab-component :tabs="tabConfig">
    <template #profile>
      <user-profile :user="currentUser" />
    </template>
    
    <template #settings>
      <user-settings @save="saveSettings" />
    </template>
    
    <template #notifications>
      <notification-list :notifications="userNotifications" />
    </template>
  </tab-component>
</template>

Conditional Slots

Checking if Slot Content Exists

<!-- Modal.vue -->
<template>
  <div class="modal" v-if="isVisible">
    <div class="modal-content">
      <!-- Header slot with conditional rendering -->
      <div v-if="$slots.header" class="modal-header">
        <slot name="header"></slot>
      </div>
      
      <!-- Default slot for body -->
      <div class="modal-body">
        <slot></slot>
      </div>
      
      <!-- Footer slot with fallback -->
      <div class="modal-footer">
        <slot name="footer">
          <button @click="$emit('close')">Close</button>
        </slot>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    isVisible: Boolean
  }
}
</script>

Renderless Components with Slots

Data Provider Component

<!-- DataProvider.vue -->
<template>
  <div>
    <slot 
      :data="data" 
      :loading="loading" 
      :error="error"
      :refresh="fetchData"
    ></slot>
  </div>
</template>

<script>
export default {
  props: {
    url: String
  },
  data() {
    return {
      data: null,
      loading: false,
      error: null
    }
  },
  async created() {
    await this.fetchData()
  },
  methods: {
    async fetchData() {
      this.loading = true
      this.error = null
      
      try {
        const response = await fetch(this.url)
        this.data = await response.json()
      } catch (err) {
        this.error = err.message
      } finally {
        this.loading = false
      }
    }
  }
}
</script>

Using Renderless Component

<template>
  <data-provider url="/api/users" v-slot="{ data, loading, error, refresh }">
    <div v-if="loading">Loading users...</div>
    <div v-else-if="error">Error: {{ error }}</div>
    <div v-else>
      <user-list :users="data" />
      <button @click="refresh">Refresh</button>
    </div>
  </data-provider>
</template>

Advanced Slot Patterns

Slot Composition Pattern

<!-- Card.vue -->
<template>
  <div class="card">
    <slot name="image"></slot>
    <div class="card-body">
      <slot name="title"></slot>
      <slot name="content"></slot>
      <slot name="actions"></slot>
    </div>
  </div>
</template>
<!-- ProductCard.vue -->
<template>
  <card>
    <template #image>
      <img :src="product.image" :alt="product.name" />
    </template>
    
    <template #title>
      <h3>{{ product.name }}</h3>
      <p class="price">${{ product.price }}</p>
    </template>
    
    <template #content>
      <p>{{ product.description }}</p>
    </template>
    
    <template #actions>
      <button @click="addToCart">Add to Cart</button>
      <button @click="addToWishlist">♥ Wishlist</button>
    </template>
  </card>
</template>

Composition API Slots

// Vue 3 Composition API
import { useSlots } from 'vue'

export default {
  setup() {
    const slots = useSlots()
    
    // Check if slots exist
    const hasHeader = !!slots.header
    const hasFooter = !!slots.footer
    
    return {
      hasHeader,
      hasFooter
    }
  }
}

Best Practices for Slots

  1. Use descriptive slot names that clearly indicate their purpose
  2. Provide fallback content for optional slots
  3. Document slot contracts - what data/props are expected
  4. Use conditional rendering to handle optional slots gracefully
  5. Keep slot APIs simple and focused
  6. Consider using scoped slots for data sharing

References:

↑ Back to top

Intermediate Topics

What are computed properties and how do they differ from methods in Vue?

Computed properties are cached reactive values that are recalculated only when their dependencies change, while methods are functions that execute every time they are called.

Key Differences:

Aspect Computed Properties Methods
Caching Cached based on dependencies No caching, executes on every call
Performance Better for expensive operations May cause unnecessary re-calculations
Reactivity Automatically updates when dependencies change Must be explicitly called
Usage For derived data that depends on reactive data For event handlers and actions

Example:

export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe',
      items: [1, 2, 3, 4, 5]
    }
  },
  computed: {
    // Cached - only recalculates when firstName or lastName changes
    fullName() {
      console.log('Computing fullName')
      return `${this.firstName} ${this.lastName}`
    },
    expensiveCalculation() {
      return this.items.reduce((sum, item) => sum + item * Math.random(), 0)
    }
  },
  methods: {
    // Executes every time it's called
    getFullName() {
      console.log('Calling getFullName method')
      return `${this.firstName} ${this.lastName}`
    }
  }
}

Reference: Vue.js Official Documentation - Computed Properties

↑ Back to top

How does Vue's reactivity system work, and what are some potential pitfalls with reactivity?

Vue's reactivity system automatically tracks dependencies and updates the UI when reactive data changes. It uses getters/setters (Vue 2) or Proxies (Vue 3) to detect changes.

How Vue 2 Reactivity Works:

  1. Object.defineProperty() converts properties to getters/setters
  2. Dependency Tracking - During render, Vue tracks which properties are accessed
  3. Change Detection - When a setter is triggered, Vue knows which components to re-render
  4. Virtual DOM Diffing - Vue calculates minimal changes needed

How Vue 3 Reactivity Works:

  1. Proxy Objects intercept property access and modifications
  2. Effect System tracks reactive dependencies automatically
  3. Ref/Reactive APIs provide explicit reactivity control

Vue 3 Example:

import { reactive, ref, computed, watchEffect } from 'vue'

export default {
  setup() {
    // Reactive object
    const state = reactive({
      count: 0,
      user: {
        name: 'John',
        email: 'john@example.com'
      }
    })
    
    // Reactive primitive
    const message = ref('Hello')
    
    // Computed property
    const doubledCount = computed(() => state.count * 2)
    
    // Watcher
    watchEffect(() => {
      console.log(`Count is: ${state.count}`)
    })
    
    return {
      state,
      message,
      doubledCount
    }
  }
}

Common Reactivity Pitfalls:

1. Vue 2: Array Index and Length Issues

// ❌ Won't trigger reactivity in Vue 2
this.items[0] = newValue
this.items.length = 0

// ✅ Correct way in Vue 2
this.$set(this.items, 0, newValue)
this.items.splice(0)

2. Vue 2: Adding New Object Properties

// ❌ Won't be reactive in Vue 2
this.user.newProperty = 'value'

// ✅ Correct way in Vue 2
this.$set(this.user, 'newProperty', 'value')
// or
this.user = { ...this.user, newProperty: 'value' }

3. Losing Reactivity by Destructuring

// ❌ Loses reactivity
const { count } = this.state
count++ // Won't update UI

// ✅ Keep reactivity
this.state.count++

4. Async Updates and nextTick

// Vue updates are asynchronous
this.message = 'Updated'
console.log(this.$el.textContent) // Still shows old value

// ✅ Use nextTick
this.message = 'Updated'
this.$nextTick(() => {
  console.log(this.$el.textContent) // Shows updated value
})

5. Memory Leaks with Watchers

// ❌ Manual watcher not cleaned up
const unwatch = this.$watch('someData', callback)
// Should call unwatch() in beforeDestroy

// ✅ Component watchers are automatically cleaned up
watch: {
  someData(newVal) {
    // Automatically cleaned up
  }
}

Performance Considerations:

  • Deep Watching can be expensive for large objects
  • Avoid Mutating Props directly
  • Use Object.freeze() for static data to prevent unnecessary reactivity

References:

↑ Back to top

How would you optimize a Vue application for performance?

Vue application performance optimization involves multiple strategies across different areas: bundle size, runtime performance, memory usage, and network efficiency.

1. Bundle Optimization

Code Splitting and Lazy Loading

// Route-based code splitting
const Home = () => import('@/views/Home.vue')
const About = () => import('@/views/About.vue')
const UserProfile = () => import(
  /* webpackChunkName: "user" */ '@/views/UserProfile.vue'
)

const router = new VueRouter({
  routes: [
    { path: '/', component: Home },
    { path: '/about', component: About },
    { path: '/profile', component: UserProfile }
  ]
})

Dynamic Component Loading

<template>
  <div>
    <!-- Load heavy component only when needed -->
    <component 
      :is="currentComponent" 
      v-if="showHeavyComponent"
    />
    
    <!-- Async component with loading state -->
    <AsyncComponent />
  </div>
</template>

<script>
const AsyncComponent = () => ({
  component: import('@/components/HeavyComponent.vue'),
  loading: LoadingComponent,
  error: ErrorComponent,
  delay: 200,
  timeout: 3000
})

export default {
  components: {
    AsyncComponent
  }
}
</script>

Tree Shaking and Dead Code Elimination

// ❌ Imports entire lodash library
import _ from 'lodash'

// ✅ Import only needed functions
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'

// ✅ Use ES6 modules for better tree shaking
import { debounce, throttle } from 'lodash-es'

2. Runtime Performance Optimization

Use v-show vs v-if Appropriately

<template>
  <!-- Use v-if for conditionally rendered content -->
  <UserSettings v-if="user.isLoggedIn" />
  
  <!-- Use v-show for frequently toggled content -->
  <Modal v-show="isModalVisible" />
</template>

Optimize List Rendering

<template>
  <!-- Always use :key for list items -->
  <div 
    v-for="item in items" 
    :key="item.id"
    class="list-item"
  >
    {{ item.name }}
  </div>
  
  <!-- Use v-memo for expensive list items (Vue 3) -->
  <div 
    v-for="item in list" 
    :key="item.id"
    v-memo="[item.id, item.selected]"
  >
    <ExpensiveChild :data="item" />
  </div>
</template>

Computed Properties vs Methods

export default {
  computed: {
    // ✅ Cached, only recalculates when dependencies change
    expensiveValue() {
      return this.items.filter(item => item.active)
                      .reduce((sum, item) => sum + item.value, 0)
    }
  },
  
  methods: {
    // ❌ Executes on every render
    getExpensiveValue() {
      return this.items.filter(item => item.active)
                      .reduce((sum, item) => sum + item.value, 0)
    }
  }
}

Use Object.freeze() for Static Data

export default {
  data() {
    return {
      // Prevent Vue from making this reactive
      staticData: Object.freeze({
        categories: ['A', 'B', 'C'],
        constants: { MAX_SIZE: 100, MIN_SIZE: 10 }
      }),
      
      // Large datasets that don't change
      chartData: Object.freeze(largeDataset)
    }
  }
}

3. Memory Management

Proper Event Listener Cleanup

export default {
  mounted() {
    window.addEventListener('resize', this.handleResize)
    this.intervalId = setInterval(this.updateData, 1000)
  },
  
  beforeDestroy() {
    // ✅ Clean up to prevent memory leaks
    window.removeEventListener('resize', this.handleResize)
    clearInterval(this.intervalId)
  },
  
  methods: {
    handleResize() {
      // Handle window resize
    }
  }
}

Use WeakMap for Private Data

const privateData = new WeakMap()

export default {
  created() {
    // Store private data that gets garbage collected
    privateData.set(this, {
      sensitiveInfo: 'secret',
      temporaryCache: new Map()
    })
  }
}

4. Asset Optimization

Image Optimization

<template>
  <!-- Use modern image formats -->
  <picture>
    <source srcset="image.webp" type="image/webp">
    <source srcset="image.avif" type="image/avif">
    <img src="image.jpg" alt="Description" loading="lazy">
  </picture>
  
  <!-- Responsive images -->
  <img 
    :src="imageSrc"
    :srcset="imageSrcSet"
    sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
    loading="lazy"
    alt="Responsive image"
  >
</template>

Font Optimization

/* Preload critical fonts */
@font-face {
  font-family: 'MyCustomFont';
  src: url('./fonts/font.woff2') format('woff2');
  font-display: swap; /* Improve perceived performance */
}

5. Virtual Scrolling for Large Lists

<template>
  <!-- Use virtual scrolling for large datasets -->
  <RecycleScroller
    class="scroller"
    :items="items"
    :item-size="50"
    key-field="id"
    v-slot="{ item }"
  >
    <div class="item">{{ item.name }}</div>
  </RecycleScroller>
</template>

<script>
import { RecycleScroller } from 'vue-virtual-scroller'

export default {
  components: {
    RecycleScroller
  }
}
</script>

6. State Management Optimization

Normalize State Structure

// ❌ Nested, hard to update
state: {
  users: [
    { id: 1, name: 'John', posts: [{ id: 1, title: 'Post 1' }] }
  ]
}

// ✅ Normalized, easier to update
state: {
  users: {
    byId: {
      1: { id: 1, name: 'John', postIds: [1] }
    },
    allIds: [1]
  },
  posts: {
    byId: {
      1: { id: 1, title: 'Post 1', userId: 1 }
    },
    allIds: [1]
  }
}

Use Vuex Modules

// Store modules for better performance
const userModule = {
  namespaced: true,
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    user: userModule,
    products: productModule
  }
})

7. Network Optimization

API Request Optimization

// Debounce search requests
import { debounce } from 'lodash-es'

export default {
  data() {
    return {
      searchQuery: '',
      searchResults: []
    }
  },
  
  watch: {
    searchQuery: debounce(function(newQuery) {
      if (newQuery.length > 2) {
        this.performSearch(newQuery)
      }
    }, 300)
  },
  
  methods: {
    async performSearch(query) {
      // Cancel previous request if still pending
      if (this.searchController) {
        this.searchController.abort()
      }
      
      this.searchController = new AbortController()
      
      try {
        const response = await fetch(`/api/search?q=${query}`, {
          signal: this.searchController.signal
        })
        this.searchResults = await response.json()
      } catch (error) {
        if (error.name !== 'AbortError') {
          console.error('Search failed:', error)
        }
      }
    }
  }
}

8. Build Configuration Optimization

// vue.config.js
const path = require('path')

module.exports = {
  // Production optimizations
  productionSourceMap: false,
  
  configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      // Optimize chunks
      config.optimization.splitChunks = {
        chunks: 'all',
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all'
          }
        }
      }
    }
  },
  
  // Enable modern mode
  modern: true,
  
  // PWA optimizations
  pwa: {
    workboxOptions: {
      runtimeCaching: [{
        urlPattern: /^https:\/\/api\./,
        handler: 'CacheFirst',
        options: {
          cacheName: 'api-cache',
          expiration: {
            maxEntries: 50,
            maxAgeSeconds: 300
          }
        }
      }]
    }
  }
}

Performance Monitoring

// Performance monitoring
export default {
  async mounted() {
    // Measure component mount time
    const startTime = performance.now()
    
    await this.loadData()
    
    const endTime = performance.now()
    console.log(`Component loaded in ${endTime - startTime}ms`)
    
    // Use Performance Observer API
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          console.log(entry.name, entry.duration)
        }
      })
      observer.observe({ entryTypes: ['measure', 'navigation'] })
    }
  }
}

Performance Measurement Tools:

  • Vue DevTools - Component performance profiling
  • Lighthouse - Web performance auditing
  • webpack-bundle-analyzer - Bundle size analysis
  • Chrome DevTools - Runtime performance profiling

References:

↑ Back to top

What are single file components (SFCs) and what advantages do they provide?

Single File Components (SFCs) are Vue.js files with a .vue extension that encapsulate the template, script, and style of a component in a single file. They provide a cohesive way to organize component-related code.

SFC Structure:

<template>
  <div class="my-component">
    <h1>{{ title }}</h1>
    <p>{{ description }}</p>
    <button @click="handleClick">{{ buttonText }}</button>
  </div>
</template>

<script>
export default {
  name: 'MyComponent',
  
  props: {
    title: {
      type: String,
      required: true
    },
    description: String
  },
  
  data() {
    return {
      buttonText: 'Click me!'
    }
  },
  
  methods: {
    handleClick() {
      this.$emit('button-clicked', this.title)
    }
  }
}
</script>

<style scoped>
.my-component {
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 8px;
}

.my-component h1 {
  color: #42b883;
  margin-bottom: 10px;
}

.my-component button {
  background-color: #42b883;
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
}

.my-component button:hover {
  background-color: #369870;
}
</style>

Key Advantages:

1. Co-location of Concerns

All component-related code is in one file, making it easier to understand and maintain:

<!-- UserCard.vue -->
<template>
  <div class="user-card">
    <img :src="user.avatar" :alt="user.name" class="avatar">
    <div class="user-info">
      <h3 class="user-name">{{ user.name }}</h3>
      <p class="user-email">{{ user.email }}</p>
      <span class="user-status" :class="statusClass">
        {{ user.status }}
      </span>
    </div>
  </div>
</template>

<script>
export default {
  name: 'UserCard',
  props: ['user'],
  
  computed: {
    statusClass() {
      return {
        'status-online': this.user.status === 'online',
        'status-offline': this.user.status === 'offline',
        'status-away': this.user.status === 'away'
      }
    }
  }
}
</script>

<style scoped>
.user-card {
  display: flex;
  padding: 15px;
  border-radius: 8px;
  background: white;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.avatar {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  margin-right: 15px;
}

.user-info {
  flex: 1;
}

.user-name {
  margin: 0 0 5px 0;
  font-size: 16px;
  font-weight: 600;
}

.user-email {
  margin: 0 0 8px 0;
  color: #666;
  font-size: 14px;
}

.user-status {
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 12px;
  text-transform: uppercase;
}

.status-online { background: #4CAF50; color: white; }
.status-offline { background: #9E9E9E; color: white; }
.status-away { background: #FF9800; color: white; }
</style>

2. Scoped CSS

CSS styles are automatically scoped to the component, preventing style conflicts:

<template>
  <div class="button">
    <span class="text">{{ label }}</span>
  </div>
</template>

<style scoped>
/* These styles only apply to this component */
.button {
  background: blue;
  color: white;
  padding: 10px;
}

.text {
  font-weight: bold;
}

/* Deep selectors for child components */
.button :deep(.child-class) {
  color: red;
}

/* Global selectors */
:global(.global-class) {
  margin: 0;
}
</style>

Generated CSS with unique scope identifiers:

.button[data-v-f3f3eg9] {
  background: blue;
  color: white;
  padding: 10px;
}

.text[data-v-f3f3eg9] {
  font-weight: bold;
}

3. CSS Pre-processor Support

SFCs support various CSS pre-processors out of the box:

<style lang="scss" scoped>
$primary-color: #42b883;
$secondary-color: #35495e;

.component {
  background: $primary-color;
  
  &:hover {
    background: darken($primary-color, 10%);
  }
  
  .nested {
    color: $secondary-color;
    
    @include respond-to(mobile) {
      font-size: 14px;
    }
  }
}
</style>

<style lang="stylus" scoped>
$primary = #42b883

.component
  background $primary
  
  &:hover
    background darken($primary, 10%)
</style>

4. Hot Module Replacement (HMR)

SFCs support efficient hot reloading during development:

  • Template changes → Re-render component in place
  • Script changes → Re-create component instances
  • Style changes → Update styles without full reload

5. Build-time Optimizations

SFCs enable various build-time optimizations:

<template>
  <!-- Build-time template compilation -->
  <div>
    <h1>{{ title }}</h1>
    <!-- Static hoisting for performance -->
    <div class="static-content">This won't change</div>
  </div>
</template>

<script>
// Tree-shaking friendly imports
import { computed, ref } from 'vue'
import { debounce } from 'lodash-es'

export default {
  setup() {
    const title = ref('Hello')
    
    // Build-time dead code elimination
    const unused = computed(() => 'This will be removed if unused')
    
    return { title }
  }
}
</script>

<style scoped>
/* Unused styles are automatically removed */
.used-class {
  color: red;
}

.unused-class {
  color: blue; /* Removed in production build */
}
</style>

6. TypeScript Support

SFCs have excellent TypeScript integration:

<template>
  <div>
    <h1>{{ title }}</h1>
    <button @click="increment">Count: {{ count }}</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, computed } from 'vue'

interface User {
  id: number
  name: string
  email: string
}

export default defineComponent({
  name: 'TypedComponent',
  
  props: {
    user: {
      type: Object as () => User,
      required: true
    }
  },
  
  setup(props) {
    const count = ref<number>(0)
    
    const title = computed<string>(() => 
      `Hello, ${props.user.name}!`
    )
    
    const increment = (): void => {
      count.value++
    }
    
    return {
      count,
      title,
      increment
    }
  }
})
</script>

7. Testing Benefits

SFCs are easier to test as complete units:

// Component.spec.js
import { shallowMount } from '@vue/test-utils'
import UserCard from '@/components/UserCard.vue'

describe('UserCard.vue', () => {
  const mockUser = {
    name: 'John Doe',
    email: 'john@example.com',
    avatar: 'avatar.jpg',
    status: 'online'
  }
  
  it('renders user information correctly', () => {
    const wrapper = shallowMount(UserCard, {
      props: { user: mockUser }
    })
    
    expect(wrapper.find('.user-name').text()).toBe('John Doe')
    expect(wrapper.find('.user-email').text()).toBe('john@example.com')
    expect(wrapper.find('.status-online').exists()).toBe(true)
  })
  
  it('applies correct status class', () => {
    const wrapper = shallowMount(UserCard, {
      props: { 
        user: { ...mockUser, status: 'offline' }
      }
    })
    
    expect(wrapper.find('.status-offline').exists()).toBe(true)
  })
})

8. IDE Support

Modern IDEs provide excellent support for SFCs:

  • Syntax Highlighting for template, script, and style blocks
  • IntelliSense for Vue APIs and component props
  • Error Detection and type checking
  • Auto-completion for directives and component names
  • Refactoring support across template and script sections

9. CSS Modules Support

<template>
  <div :class="$style.wrapper">
    <h1 :class="$style.title">{{ title }}</h1>
  </div>
</template>

<style module>
.wrapper {
  padding: 20px;
}

.title {
  color: #42b883;
}
</style>

10. Multiple Style Blocks

<template>
  <div class="component">
    <h1>Title</h1>
  </div>
</template>

<!-- Global styles -->
<style>
body {
  font-family: Arial, sans-serif;
}
</style>

<!-- Scoped styles -->
<style scoped>
.component {
  padding: 20px;
}
</style>

<!-- CSS modules -->
<style module="classes">
.special {
  background: yellow;
}
</style>

Comparison with Traditional Approach:

Aspect SFCs Traditional Separation
Organization Co-located in one file Separate HTML/CSS/JS files
Maintenance Easier to modify related code Need to navigate multiple files
Scoping Automatic CSS scoping Manual class naming conventions
Hot Reload Granular updates Full page reload
Build Tools Integrated processing Manual configuration
Type Safety Full TypeScript support Limited cross-file checking

Build Process: SFCs are processed by the Vue compiler during build time:

  1. Template → Render function
  2. Script → Component options/setup function
  3. Style → Scoped CSS with unique identifiers
  4. Optimization → Dead code elimination, tree shaking

References:

↑ Back to top

State Management

What is Vuex and why would you use it in a project?

Vuex is the official state management library for Vue.js applications. It serves as a centralized store for all components in an application, implementing a predictable state container pattern similar to Redux in React or Flux architecture.

Why use Vuex?

  1. Centralized State Management: Vuex provides a single source of truth for your application's state, making it easier to track and debug state changes across complex applications.

  2. Predictable State Changes: All state mutations happen through well-defined channels (mutations), making the application's behavior more predictable and easier to reason about.

  3. Component Communication: Eliminates the need for complex prop drilling or event bubbling between deeply nested components.

  4. Time-Travel Debugging: Integration with Vue DevTools allows you to inspect state changes and even revert to previous states.

  5. SSR Support: Works seamlessly with server-side rendering applications.

Vuex is particularly beneficial in medium to large-scale applications where multiple components need to share and modify the same state.

References:

↑ Back to top

Routing and Navigation

What is Vue Router and why is it important in single-page applications?

Vue Router is the official routing library for Vue.js applications. It provides a declarative way to handle client-side routing in single-page applications (SPAs).

Key characteristics of Vue Router:

  • Client-side routing: Manages navigation between different views without full page reloads
  • URL synchronization: Keeps the browser URL in sync with the application state
  • History management: Provides browser history support with back/forward buttons
  • Component-based: Routes are mapped to Vue components

Importance in SPAs:

  • User Experience: Enables fast navigation without page refreshes, creating a smooth, app-like experience
  • SEO Benefits: Supports HTML5 history mode for clean URLs and better search engine indexing
  • State Management: Maintains application state during navigation
  • Deep Linking: Allows users to bookmark and share specific application states via URLs
// Basic Vue Router setup
import { createRouter, createWebHistory } from 'vue-router'
import Home from './components/Home.vue'
import About from './components/About.vue'

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

References:

↑ Back to top

What are navigation guards and how do they help in controlling access to routes?

Navigation guards are functions that allow you to control navigation flow in Vue Router applications. They act as middleware that can prevent, redirect, or modify navigation attempts.

Types of Navigation Guards:

1. Global Guards

// Global before guard
router.beforeEach((to, from, next) => {
  // Authentication check
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!isAuthenticated()) {
      next('/login')
      return
    }
  }
  next()
})

// Global resolve guard
router.beforeResolve((to, from, next) => {
  // Called after all component guards and async route components are resolved
  next()
})

// Global after hook
router.afterEach((to, from) => {
  // Analytics tracking, page titles, etc.
  document.title = to.meta.title || 'Default Title'
})

2. Per-Route Guards

const routes = [
  {
    path: '/admin',
    component: Admin,
    beforeEnter: (to, from, next) => {
      if (!hasAdminRole()) {
        next('/unauthorized')
        return
      }
      next()
    }
  }
]

3. In-Component Guards

export default {
  beforeRouteEnter(to, from, next) {
    // Called before route is confirmed
    // No access to `this` component instance
    next(vm => {
      // Access to component instance via `vm`
    })
  },
  
  beforeRouteUpdate(to, from, next) {
    // Called when route changes but component is reused
    // Has access to `this`
    this.fetchData(to.params.id)
    next()
  },
  
  beforeRouteLeave(to, from, next) {
    // Called when navigating away from current route
    if (this.hasUnsavedChanges) {
      const answer = window.confirm('You have unsaved changes. Leave anyway?')
      if (answer) {
        next()
      } else {
        next(false)
      }
    } else {
      next()
    }
  }
}

Common Use Cases:

  1. Authentication & Authorization:
router.beforeEach((to, from, next) => {
  const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
  const currentUser = getCurrentUser()
  
  if (requiresAuth && !currentUser) {
    next('/login')
  } else if (to.path === '/login' && currentUser) {
    next('/')
  } else {
    next()
  }
})
  1. Data Fetching:
beforeRouteEnter(to, from, next) {
  fetchUserData(to.params.id).then(user => {
    next(vm => vm.setUser(user))
  }).catch(() => next('/404'))
}
  1. Form Validation:
beforeRouteLeave(to, from, next) {
  if (this.formDirty && !window.confirm('Discard changes?')) {
    next(false)
  } else {
    next()
  }
}

Navigation Guard Flow:

  1. Navigation triggered
  2. Call leave guards in deactivated components
  3. Call global beforeEach guards
  4. Call beforeRouteUpdate guards in reused components
  5. Call beforeEnter in route configs
  6. Resolve async route components
  7. Call beforeRouteEnter in activated components
  8. Call global beforeResolve guards
  9. Navigation confirmed
  10. Call global afterEach hooks

References:

↑ Back to top

Composition API and Advanced Patterns

What is the Composition API and how does it differ from the Options API?

The Composition API is a new way to organize and reuse component logic in Vue 3, introduced as an alternative to the traditional Options API. It provides a more flexible and powerful approach to building Vue components.

Key Differences:

Options API Composition API
Logic organized by option types (data, methods, computed, etc.) Logic organized by feature/concern
Uses this context Uses explicit imports and function calls
Less flexible for code reuse Highly composable and reusable
Better for beginners More powerful for complex applications

Options API Example:

export default {
  data() {
    return {
      count: 0,
      message: 'Hello'
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}

Composition API Example:

import { ref, computed } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const message = ref('Hello')
    
    const doubleCount = computed(() => count.value * 2)
    
    const increment = () => {
      count.value++
    }
    
    return {
      count,
      message,
      doubleCount,
      increment
    }
  }
}

References:

↑ Back to top

Can you explain how to reuse code across components with Composition API's custom hooks (composables)?

Composables are reusable functions that encapsulate stateful logic using the Composition API. They follow the naming convention of starting with "use" (e.g., useCounter, useAuth).

Basic Composable Structure

// composables/useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue
  
  const isEven = computed(() => count.value % 2 === 0)
  const isPositive = computed(() => count.value > 0)
  
  return {
    // State
    count: readonly(count), // Make it readonly if needed
    
    // Computed
    isEven,
    isPositive,
    
    // Actions
    increment,
    decrement,
    reset
  }
}

Using Composables in Components

// components/CounterComponent.vue
import { useCounter } from '@/composables/useCounter'

export default {
  setup() {
    const { count, isEven, increment, decrement, reset } = useCounter(10)
    
    return {
      count,
      isEven,
      increment,
      decrement,
      reset
    }
  }
}

Advanced Composables Examples

1. API Fetching Composable

// composables/useFetch.js
import { ref, watchEffect } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)
  
  const fetchData = async () => {
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch(url.value || url)
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      data.value = await response.json()
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }
  
  // Auto-fetch when URL changes
  watchEffect(() => {
    if (url) {
      fetchData()
    }
  })
  
  return {
    data: readonly(data),
    error: readonly(error),
    loading: readonly(loading),
    refetch: fetchData
  }
}

// Usage
export default {
  setup() {
    const userId = ref(1)
    const userUrl = computed(() => `/api/users/${userId.value}`)
    
    const { data: user, loading, error, refetch } = useFetch(userUrl)
    
    return { user, loading, error, refetch, userId }
  }
}

2. Local Storage Composable

// composables/useLocalStorage.js
import { ref, watch } from 'vue'

export function useLocalStorage(key, defaultValue) {
  const storedValue = localStorage.getItem(key)
  const initialValue = storedValue ? JSON.parse(storedValue) : defaultValue
  
  const value = ref(initialValue)
  
  // Watch for changes and update localStorage
  watch(value, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue))
  }, { deep: true })
  
  // Method to remove from localStorage
  const remove = () => {
    localStorage.removeItem(key)
    value.value = defaultValue
  }
  
  return {
    value,
    remove
  }
}

// Usage
export default {
  setup() {
    const { value: theme, remove: clearTheme } = useLocalStorage('theme', 'light')
    const { value: userPrefs } = useLocalStorage('userPreferences', {
      language: 'en',
      notifications: true
    })
    
    return { theme, clearTheme, userPrefs }
  }
}

3. Form Validation Composable

// composables/useForm.js
import { ref, reactive, computed } from 'vue'

export function useForm(initialValues = {}, validationRules = {}) {
  const values = reactive({ ...initialValues })
  const errors = reactive({})
  const touched = reactive({})
  
  const validate = (field) => {
    const rule = validationRules[field]
    if (rule) {
      const error = rule(values[field])
      if (error) {
        errors[field] = error
      } else {
        delete errors[field]
      }
    }
  }
  
  const validateAll = () => {
    Object.keys(validationRules).forEach(validate)
  }
  
  const handleChange = (field, value) => {
    values[field] = value
    touched[field] = true
    validate(field)
  }
  
  const isValid = computed(() => Object.keys(errors).length === 0)
  const hasErrors = computed(() => Object.keys(errors).length > 0)
  
  const reset = () => {
    Object.assign(values, initialValues)
    Object.keys(errors).forEach(key => delete errors[key])
    Object.keys(touched).forEach(key => delete touched[key])
  }
  
  return {
    values,
    errors,
    touched,
    isValid,
    hasErrors,
    handleChange,
    validate,
    validateAll,
    reset
  }
}

// Usage
export default {
  setup() {
    const { values, errors, isValid, handleChange, validateAll } = useForm(
      { email: '', password: '' },
      {
        email: (value) => {
          if (!value) return 'Email is required'
          if (!/\S+@\S+\.\S+/.test(value)) return 'Email is invalid'
          return null
        },
        password: (value) => {
          if (!value) return 'Password is required'
          if (value.length < 6) return 'Password must be at least 6 characters'
          return null
        }
      }
    )
    
    const submit = () => {
      validateAll()
      if (isValid.value) {
        // Submit form
        console.log('Form submitted:', values)
      }
    }
    
    return {
      values,
      errors,
      isValid,
      handleChange,
      submit
    }
  }
}

4. Mouse Position Composable

// composables/useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)
  
  const updateMouse = (event) => {
    x.value = event.pageX
    y.value = event.pageY
  }
  
  onMounted(() => {
    window.addEventListener('mousemove', updateMouse)
  })
  
  onUnmounted(() => {
    window.removeEventListener('mousemove', updateMouse)
  })
  
  return { x: readonly(x), y: readonly(y) }
}

Composable Composition Pattern

// composables/useUserProfile.js
import { computed } from 'vue'
import { useFetch } from './useFetch'
import { useLocalStorage } from './useLocalStorage'

export function useUserProfile(userId) {
  // Compose multiple composables
  const { data: user, loading, error } = useFetch(`/api/users/${userId}`)
  const { value: cachedPreferences } = useLocalStorage('userPrefs', {})
  
  const fullName = computed(() => {
    return user.value ? `${user.value.firstName} ${user.value.lastName}` : ''
  })
  
  const preferences = computed(() => {
    return { ...cachedPreferences.value, ...user.value?.preferences }
  })
  
  return {
    user,
    loading,
    error,
    fullName,
    preferences
  }
}

Best Practices for Composables

  1. Naming Convention: Always start with "use"
  2. Return Consistent Objects: Return an object with descriptive property names
  3. Use readonly(): For state that shouldn't be mutated externally
  4. Accept Parameters: Make composables configurable
  5. Handle Cleanup: Use lifecycle hooks for cleanup when necessary
  6. Compose Together: Combine multiple composables for complex functionality

References:

↑ Back to top

Want more questions?

You've seen 15 sample questions. Unlock all 48 En interview questions with detailed explanations, code examples, and expert insights.

48+ questions
Code examples
Expert explanations
Instant access
Unlock Full Access