1593 words
8 minutes
Vue.js Power Tips: Level Up Your Frontend Development

Cover image source: Source

Vue.js Power Tips: Level Up Your Frontend Development ⚑#

Hello fellow Vue enthusiasts! After working with Vue.js for several years across dozens of projects, I’ve collected some incredibly useful patterns and techniques that have significantly improved my development experience. Today, I’m thrilled to share these Vue.js power tips that will help take your frontend skills to the next level! πŸ’š

Why I’m Still in Love with Vue.js in 2025 🌟#

Before diving into the tips, let me share why Vue continues to be my framework of choice:

  • The progressive adoption approach means you can integrate Vue incrementally into existing projects
  • The Composition API gives us incredible flexibility while maintaining readability
  • Vue’s reactivity system feels almost magical yet remains predictable
  • The developer experience with Vue DevTools and the ecosystem is second to none
  • The community is both supportive and innovative

Vue strikes that perfect balance between power and simplicity that makes development a joy rather than a chore. Now, let’s get to those tips!

1. Supercharge Your Component Communication πŸ”„#

Global Event Bus Alternative#

While Vue 2’s event bus pattern is now discouraged, we can create a more type-safe communication system using a dedicated events service:

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

export const createEventsService = () => {
  const events = ref(new Map())
  
  const emit = (event, payload) => {
    if (!events.value.has(event)) return
    events.value.get(event).forEach(callback => callback(payload))
  }
  
  const on = (event, callback) => {
    if (!events.value.has(event)) {
      events.value.set(event, new Set())
    }
    events.value.get(event).add(callback)
    
    // Return unsubscribe function
    return () => {
      if (events.value.has(event)) {
        events.value.get(event).delete(callback)
      }
    }
  }
  
  return { emit, on }
}

// Create a singleton instance
export const eventsService = createEventsService()

This pattern gives you better control over event handling while maintaining loose coupling between components.

Pro Tip! πŸ’‘#

Always remember to clean up event listeners in onUnmounted() to prevent memory leaks:

import { onMounted, onUnmounted } from 'vue'
import { eventsService } from './events'

export default {
  setup() {
    let unsubscribe
    
    onMounted(() => {
      unsubscribe = eventsService.on('my-event', handleEvent)
    })
    
    onUnmounted(() => {
      unsubscribe && unsubscribe()
    })
    
    const handleEvent = (payload) => {
      console.log('Event received:', payload)
    }
    
    return { handleEvent }
  }
}

2. Composition API Patterns That Changed My Life 🧩#

The Composition API is incredibly powerful, but these specific patterns have truly transformed my development workflow:

Custom Composables for Form Validation#

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

export function useForm(initialState, validations) {
  const formData = ref({...initialState})
  const errors = ref({})
  
  const validate = () => {
    const newErrors = {}
    let isValid = true
    
    Object.keys(validations).forEach(field => {
      const fieldValidators = validations[field]
      
      for (const validator of fieldValidators) {
        const { isValid: fieldIsValid, message } = validator(formData.value[field])
        
        if (!fieldIsValid) {
          if (!newErrors[field]) newErrors[field] = []
          newErrors[field].push(message)
          isValid = false
          break
        }
      }
    })
    
    errors.value = newErrors
    return isValid
  }
  
  const resetForm = () => {
    formData.value = {...initialState}
    errors.value = {}
  }
  
  const isValid = computed(() => Object.keys(errors.value).length === 0)
  
  return {
    formData,
    errors,
    validate,
    resetForm,
    isValid
  }
}

Usage in a component:

import { useForm } from '@/composables/useForm'

export default {
  setup() {
    const { formData, errors, validate, resetForm } = useForm(
      { email: '', password: '' },
      {
        email: [
          value => ({ 
            isValid: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), 
            message: 'Please enter a valid email address'
          })
        ],
        password: [
          value => ({ 
            isValid: value.length >= 8, 
            message: 'Password must be at least 8 characters'
          })
        ]
      }
    )
    
    const handleSubmit = () => {
      if (validate()) {
        // Submit the form
      }
    }
    
    return { formData, errors, handleSubmit, resetForm }
  }
}

State Machines for Complex UI Logic#

When managing complex component states, mini state machines can dramatically simplify your code:

import { ref, computed } from 'vue'

export function useStateMachine(initialState, transitions) {
  const currentState = ref(initialState)
  
  const can = (action) => {
    return transitions[currentState.value]?.includes(action)
  }
  
  const when = computed(() => {
    return Object.keys(transitions).reduce((acc, state) => {
      acc[state] = currentState.value === state
      return acc
    }, {})
  })
  
  const transition = (action) => {
    if (!can(action)) {
      console.warn(`Invalid transition: ${action} from state ${currentState.value}`)
      return false
    }
    
    currentState.value = action
    return true
  }
  
  return {
    state: currentState,
    can,
    when,
    transition
  }
}

Using it for a multi-step form:

const { state, when, transition } = useStateMachine('personalInfo', {
  'personalInfo': ['contactInfo', 'cancel'],
  'contactInfo': ['review', 'personalInfo', 'cancel'],
  'review': ['submit', 'contactInfo', 'cancel'],
  'submitting': ['success', 'error'],
  'success': [],
  'error': ['retry', 'cancel']
})

In your template:

<div v-if="when.personalInfo">
  <!-- Personal Info Form -->
  <button @click="transition('contactInfo')">Next</button>
</div>

<div v-else-if="when.contactInfo">
  <!-- Contact Info Form -->
  <button @click="transition('personalInfo')">Back</button>
  <button @click="transition('review')">Next</button>
</div>

<!-- ... and so on -->

3. Performance Optimization Techniques That Actually Work πŸš€#

Dynamic Component Loading#

Lazy-load components only when needed to improve initial load times:

import { defineAsyncComponent } from 'vue'

// In your component or router
const HeavyComponent = defineAsyncComponent(() => 
  import('./HeavyComponent.vue')
)

// With loading and error states
const ComplexChart = defineAsyncComponent({
  loader: () => import('./ComplexChart.vue'),
  loadingComponent: LoadingSpinner,
  errorComponent: ErrorDisplay,
  delay: 200,
  timeout: 10000
})

Component-Level Code Splitting#

Rather than creating a monolithic bundle, split functionality by routes:

// router/index.js
const routes = [
  {
    path: '/dashboard',
    component: () => import('@/views/Dashboard.vue'),
    children: [
      {
        path: 'analytics',
        component: () => import('@/views/Analytics.vue')
      },
      {
        path: 'reports',
        component: () => import('@/views/Reports.vue')
      }
    ]
  }
]

Virtual Scrolling for Long Lists#

For extremely long lists, implement virtual scrolling for massive performance gains:

<template>
  <RecycleScroller
    class="scroller"
    :items="hugeDataset"
    :item-size="32"
    key-field="id"
    v-slot="{ item }"
  >
    <div class="user-item">
      {{ item.name }}
    </div>
  </RecycleScroller>
</template>

<script setup>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'

// Your data setup
</script>

4. Testing Strategies That Save Time βœ…#

Component Testing with Vitest and Vue Test Utils#

One testing approach that has saved me countless hours:

import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import UserProfile from './UserProfile.vue'

describe('UserProfile', () => {
  it('displays user information correctly', () => {
    const wrapper = mount(UserProfile, {
      props: {
        user: {
          name: 'Jane Doe',
          email: 'jane@example.com'
        }
      }
    })
    
    expect(wrapper.text()).toContain('Jane Doe')
    expect(wrapper.text()).toContain('jane@example.com')
  })
  
  it('emits update event when form is submitted', async () => {
    const wrapper = mount(UserProfile, {
      props: {
        user: {
          name: 'Jane Doe',
          email: 'jane@example.com'
        }
      }
    })
    
    // Find the input and update it
    const nameInput = wrapper.find('input[name="name"]')
    await nameInput.setValue('Jane Smith')
    
    // Submit the form
    await wrapper.find('form').trigger('submit')
    
    // Check for the emitted event
    expect(wrapper.emitted('update')).toBeTruthy()
    expect(wrapper.emitted('update')[0][0].name).toBe('Jane Smith')
  })
})

Testing Composables Directly#

Composables can be tested in isolation without mounting components:

import { describe, it, expect, beforeEach } from 'vitest'
import { useCounter } from './useCounter'

describe('useCounter', () => {
  let counter
  
  beforeEach(() => {
    counter = useCounter(0)
  })
  
  it('should increment the count', () => {
    expect(counter.count.value).toBe(0)
    counter.increment()
    expect(counter.count.value).toBe(1)
  })
  
  it('should decrement the count', () => {
    counter.increment()
    counter.increment()
    expect(counter.count.value).toBe(2)
    counter.decrement()
    expect(counter.count.value).toBe(1)
  })
})

5. Vue 3 Features You Might Have Missed πŸ’Ž#

Teleport for Modal Dialogs#

Creating accessible modals is now much easier:

<button @click="showModal = true">Open Modal</button>

<teleport to="body">
  <div v-if="showModal" class="modal">
    <div class="modal-content">
      <h2>Important Information</h2>
      <p>This modal is teleported to the body element!</p>
      <button @click="showModal = false">Close</button>
    </div>
  </div>
</teleport>

Multiple v-models for Complex Forms#

<CustomForm
  v-model:first-name="firstName"
  v-model:last-name="lastName"
  v-model:email="email"
/>

In the CustomForm component:

<template>
  <div>
    <input 
      :value="firstName"
      @input="$emit('update:first-name', $event.target.value)"
    />
    <input 
      :value="lastName"
      @input="$emit('update:last-name', $event.target.value)"
    />
    <input 
      :value="email"
      @input="$emit('update:email', $event.target.value)"
    />
  </div>
</template>

<script setup>
defineProps({
  firstName: String,
  lastName: String,
  email: String
})

defineEmits(['update:first-name', 'update:last-name', 'update:email'])
</script>

Suspense for Async Components#

Handle loading states more gracefully:

<Suspense>
  <template #default>
    <AsyncComponent />
  </template>
  <template #fallback>
    <div class="loading">Loading...</div>
  </template>
</Suspense>

Where AsyncComponent might look like:

<script setup>
import { ref } from 'vue'

const data = await fetch('/api/data').then(r => r.json())
const processedData = ref(data.map(item => /* process item */))
</script>

<template>
  <div>
    <!-- Render your data -->
    <div v-for="item in processedData" :key="item.id">
      {{ item.name }}
    </div>
  </div>
</template>

6. My Daily Development Workflow Setup πŸ› οΈ#

Here’s the exact setup I use for Vue.js development that has dramatically improved my productivity:

VS Code Extensions#

  • Volar - Vue Language Features
  • ESLint
  • Prettier
  • CodeSnap (for sharing code snippets)
  • GitLens
  • Vue VSCode Snippets

Vue CLI Configuration#

My vue.config.js for optimal development:

const path = require('path')

module.exports = {
  configureWebpack: {
    resolve: {
      alias: {
        '@components': path.resolve(__dirname, 'src/components'),
        '@composables': path.resolve(__dirname, 'src/composables'),
        '@assets': path.resolve(__dirname, 'src/assets')
      }
    }
  },
  css: {
    loaderOptions: {
      sass: {
        additionalData: `
          @import "@/styles/_variables.scss";
          @import "@/styles/_mixins.scss";
        `
      }
    }
  },
  devServer: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:8000',
        changeOrigin: true
      }
    }
  }
}

Project Structure That Scales#

src/
  β”œβ”€β”€ assets/
  β”œβ”€β”€ components/
  β”‚   β”œβ”€β”€ common/
  β”‚   β”‚   β”œβ”€β”€ BaseButton.vue
  β”‚   β”‚   └── BaseInput.vue
  β”‚   β”œβ”€β”€ layout/
  β”‚   └── features/
  β”‚       β”œβ”€β”€ UserDashboard/
  β”‚       β”‚   β”œβ”€β”€ UserStats.vue
  β”‚       β”‚   └── UserActivity.vue
  β”‚       └── Settings/
  β”œβ”€β”€ composables/
  β”‚   β”œβ”€β”€ useApi.js
  β”‚   β”œβ”€β”€ useAuth.js
  β”‚   └── useNotification.js
  β”œβ”€β”€ router/
  β”œβ”€β”€ stores/
  β”œβ”€β”€ styles/
  β”‚   β”œβ”€β”€ _variables.scss
  β”‚   └── _mixins.scss
  β”œβ”€β”€ utils/
  └── views/

My Personal β€œAha!” Moment With Vue.js πŸ’­#

When I first started with Vue, I struggled with managing complex state across deep component hierarchies. I tried various patterns - Props drilling, Vuex for everything, event bus madness - none of them felt right for all situations.

The moment that changed everything was when I realized Vue wasn’t forcing me into a single pattern. I could use:

  • Props/events for parent-child communication
  • Provide/inject for distant but related components
  • Pinia/Vuex for truly global state
  • Composables for reusable logic

This flexibility to choose the right tool for each specific scenario was a revelation! It meant I could keep my components clean and focused while still maintaining clear data flow throughout the application.

Let’s Wrap Up With a Vue.js One-Liner! πŸ“#

Here’s a clever Vue technique that always brings a smile to my face:

// Using computed property getter/setter for two-way binding magic
const name = computed({
  get: () => store.state.user.name,
  set: (value) => store.commit('updateUserName', value)
})

This simple pattern creates seamless integration between local component state and your store. Beautiful, isn’t it? πŸ’š


γ‚γ‚ŠγŒγ¨γ†γ”γ–γ„γΎγ™οΌ(Thank you!) for joining me for this collection of Vue.js tips and tricks! If you have any questions or your own tips to share, please leave a comment below.

Happy coding, and may your components always render flawlessly! πŸš€

πŸ’» Yonnon

Vue.js Power Tips: Level Up Your Frontend Development
https://yonnon.site/posts/programming/vuejs-power-tips/
Author
Tran Anh Khoa
Published at
2025-04-19