تطوير الويب 02 Jul 2026 · 8 min read

Vue 3 for Developers Coming From Other Frameworks: The Composition API, Practically

A practical guide to Vue 3's Composition API for developers coming from React or Options API: ref, computed, watch, and Composables, with code examples linking to what you already know.

Vue 3 for Developers Coming From Other Frameworks: The Composition API, Practically

If you are coming to Vue from React, Angular, or even from the old Options API, the first thing you will encounter in Vue 3 is the Composition API with the script setup syntax. At first glance it may look different and confusing, but at its core it is simpler than it seems, and closer to writing plain JavaScript than you are used to. This guide takes you from scratch with a practical mindset, linking each concept to what you already know from other frameworks.

Why Composition API at All?

In the old style (Options API), you organized code by its type: all data in data, all functions in methods, derived values in computed. The idea seems tidy, but it falls apart in large components: the logic of a single feature scatters across four different sections. The Composition API flips the equation: you organize code by "logical concern" rather than by type, so everything related to a particular feature gathers in one place. The result is code that is easier to read, and more importantly: logic that is extractable and reusable.

If you come from the React world, the most accurate analogy is that the Composition API is the counterpart of Hooks, but without their annoying constraints: no sensitivity to call order, no "dependency array" chasing you, and no "stale closures" problem. Reactive functions in Vue are called once when the component is created, not on every update.

The First Building Block: ref for Simple Values

Everything starts with reactivity. To make a value "reactive" (so the UI updates automatically when it changes), you wrap it in ref:

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

const count = ref(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">Counter: {{ count }}</button>
</template>

Note two points that confuse beginners. First: inside script you access the value via count.value, but in the template you write count directly, because Vue "unwraps" it automatically there. Second: there is no "set state" function (setState) as in React; you modify count.value directly and Vue handles the rest.

When ref and When reactive?

There are two ways to create reactive state. ref works for any type (numbers, strings, objects, arrays) and requires .value in code. And reactive is dedicated to objects and arrays only, returning a "Proxy" that tracks nested properties, without .value:

import { ref, reactive } from 'vue'

const name = ref('Ahmed')       // simple → ref
const user = reactive({         // object → reactive (optional)
  name: 'Ahmed',
  age: 30,
})

user.age++   // direct modification without .value

The practical advice that puts a beginner at ease: use ref by default for everything. It is consistent (always .value in code), and avoids reactive's pitfalls like losing reactivity when destructuring the object. Many seasoned developers stick to ref alone.

Derived Values: computed

When you need a value computed from other values that updates automatically with them, use computed instead of a regular function. The important difference: computed "caches" its result and does not recompute unless its dependencies actually change:

import { ref, computed } from 'vue'

const price = ref(100)
const quantity = ref(3)

const total = computed(() => price.value * quantity.value)
// total.value = 300, and updates automatically when price or quantity changes

A simple rule: if the value is "derived" from other state, it is computed, not ref. Do not update it manually; let Vue compute it.

Watching and Lifecycle

Sometimes you want to "do something" when a value changes (not just derive a value), like calling an API. This is where watch comes in. As for dealing with the component's life moments (like fetching data when it appears), it is via lifecycle hooks like onMounted:

import { ref, watch, onMounted } from 'vue'

const query = ref('')
const results = ref([])

// watch the search change
watch(query, async (newQuery) => {
  results.value = await search(newQuery)
})

// run once when the component appears
onMounted(() => {
  console.log('Component ready')
})

If you come from React, onMounted resembles useEffect with an empty dependency array, and watch resembles useEffect with specific dependencies, but with clearer, less error-prone syntax.

The Real Gem: Composables

Here the actual power of the Composition API shines. A "composable" is a regular function that wraps reusable reactive logic across components, exactly like Hooks in React but without their constraints. The convention is for its name to start with use:

// useCounter.js
import { ref } from 'vue'

export function useCounter(initial = 0) {
  const count = ref(initial)
  const increment = () => count.value++
  const decrement = () => count.value--
  return { count, increment, decrement }
}

Then you use it in any component with one line:

<script setup>
import { useCounter } from './useCounter'

const { count, increment } = useCounter(10)
</script>

This pattern is what makes code genuinely scalable: authentication logic, data fetching, and form validation all turn into composables you write once and use across your whole project.

Communicating With the Parent: defineProps and defineEmits

To receive data from the parent component, or send events to it, script setup provides two ready macros you do not need to import:

<script setup>
// receive props from the parent
const props = defineProps({
  title: { type: String, required: true },
  count: { type: Number, default: 0 },
})

// send events to the parent
const emit = defineEmits(['save', 'cancel'])

function handleSave() {
  emit('save', props.title)
}
</script>

And if you use TypeScript, you can define types directly with cleaner syntax via defineProps with a type parameter, one of Vue 3's most powerful modern features. And since version 3.5, destructuring props automatically preserves their reactivity.

A Practical Takeaway for the Transition

If you want a rule to start with immediately: always use script setup (cleaner and faster), default to ref, make derived values computed, and move recurring logic into composables early. Do not try to memorize every API at once; start with ref, computed, and onMounted, as they cover 80% of your daily needs, and add the rest when you actually need it. And the most important mental shift for those coming from Options API or elsewhere: stop thinking "where do I put this code?", and start asking "which logic belongs to which feature?" — for when each piece gathers with its siblings, the code becomes readable and reusable effortlessly.

Was this article helpful?

Share this article

1 share

Tags: #Vue#Vue 3#Composition API#script setup#تطوير الويب#الواجهة الأمامية

More articles