Skip to main content

Command Palette

Search for a command to run...

Refactoring a Vue 2 Component to Vue 3

Updated
3 min read
Refactoring a Vue 2 Component to Vue 3
G

I am a Software developer and I'm passionate about learning new things.

A Countdown Timer Example

One of the clearest ways to understand Vue 3 is to refactor an existing Vue 2 component. Not something complex. Not something abstract. Just a small, real component that already works.

In this article, we’ll refactor a simple countdown timer from Vue 2 to Vue 3 using the Composition API, step by step, without rushing or overengineering.


The Vue 2 Component (Before)

Let’s start with a basic countdown timer built with the Options API. It counts down from 10 to 0 when a button is clicked.

<template>
  <div>
    <p>Time left: {{ time }}</p>
    <button @click="startCountdown">Start</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      time: 10,
      timer: null
    }
  },
  methods: {
    startCountdown() {
      if (this.timer) return

      this.timer = setInterval(() => {
        if (this.time > 0) {
          this.time--
        } else {
          clearInterval(this.timer)
          this.timer = null
        }
      }, 1000)
    }
  }
}
</script>

This component works. But even in this small example, you can already see state, behaviour, and side effects starting to mix.


Step 1: Identify the Logic

Before refactoring, pause and ask what this component actually does.

• It stores the countdown state (time)
• It manages a side effect (setInterval)
• It controls when the timer starts and stops

This step matters because the Composition API is about intentionally grouping related logic.


Step 2: Move the Logic to Vue 3

In Vue 3, when using the Composition API, component logic lives inside setup() or <script setup>.

This is where we’ll define state, functions, and side effects clearly.


The Vue 3 Component (After)

Here’s the same countdown timer refactored using the Composition API.

<template>
  <div>
    <p>Time left: {{ time }}</p>
    <button @click="startCountdown">Start</button>
  </div>
</template>

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

const time = ref(10)
let timer = null

function startCountdown() {
  if (timer) return

  timer = setInterval(() => {
    if (time.value > 0) {
      time.value--
    } else {
      clearInterval(timer)
      timer = null
    }
  }, 1000)
}

onUnmounted(() => {
  if (timer) {
    clearInterval(timer)
  }
})
</script>

At first glance, this might look different. But the intent is the same.


What Changed and Why

Let’s break it down.

data() becomes ref()
methods become regular functions
this disappears completely
• Lifecycle logic is explicit and imported
• Related logic lives together

The timer logic is now easier to see, reason about, and extend.


Why This Scales Better

As components grow, side effects like timers, listeners, or subscriptions can become hard to track.

With the Composition API, you can:
• Group timer logic in one place
• Extract it later into a reusable composable
• See cleanup logic clearly

This isn’t about fewer lines of code. It’s about a clearer intent.


A Practical Migration Tip

Don’t start migration with your most complex components.

Pick something small like a timer, a toggle, or a form. Refactor it. Let the mental model sink in. Then move on.

Learning Vue 3 is much easier when you build confidence gradually.


Final Thoughts

Refactoring to Vue 3 isn’t about rewriting everything. It’s about expressing the same logic in a clearer, more maintainable way.

If you understand what your component does, the Composition API gives you better tools to express it.

2 views