Vue’s Composition API RFC dropped last week. It’s the biggest change to Vue since 2.0.

The Problem

Options API works great for small components:

export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    console.log('mounted')
  }
}

But for large components, related logic is scattered:

export default {
  data() {
    return {
      // User feature
      user: null,
      userLoading: false,
      
      // Posts feature
      posts: [],
      postsLoading: false,
      
      // Comments feature
      comments: [],
      commentsLoading: false
    }
  },
  methods: {
    // User feature
    async fetchUser() { },
    
    // Posts feature
    async fetchPosts() { },
    
    // Comments feature
    async fetchComments() { }
  },
  mounted() {
    // User feature
    this.fetchUser()
    
    // Posts feature
    this.fetchPosts()
    
    // Comments feature
    this.fetchComments()
  }
}

Logic for one feature is split across data, methods, and lifecycle hooks.

The Solution: Composition API

Group related logic together:

import { ref, onMounted } from 'vue'

export default {
  setup() {
    // User feature
    const user = ref(null)
    const userLoading = ref(false)
    
    async function fetchUser() {
      userLoading.value = true
      user.value = await api.getUser()
      userLoading.value = false
    }
    
    onMounted(fetchUser)
    
    // Posts feature
    const posts = ref([])
    const postsLoading = ref(false)
    
    async function fetchPosts() {
      postsLoading.value = true
      posts.value = await api.getPosts()
      postsLoading.value = false
    }
    
    onMounted(fetchPosts)
    
    return {
      user,
      userLoading,
      posts,
      postsLoading
    }
  }
}

All user-related logic is together. All posts-related logic is together.

Composition Functions

Extract logic into reusable functions:

// useUser.js
import { ref, onMounted } from 'vue'

export function useUser() {
  const user = ref(null)
  const loading = ref(false)
  const error = ref(null)
  
  async function fetch() {
    loading.value = true
    try {
      user.value = await api.getUser()
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  }
  
  onMounted(fetch)
  
  return {
    user,
    loading,
    error,
    refetch: fetch
  }
}

// Component.vue
import { useUser } from './useUser'

export default {
  setup() {
    const { user, loading, error, refetch } = useUser()
    
    return {
      user,
      loading,
      error,
      refetch
    }
  }
}

Real-World Example

We refactored our user profile component:

Before (Options API): 300 lines, logic scattered
After (Composition API): 150 lines, logic organized

// useUserProfile.js
export function useUserProfile(userId) {
  const user = ref(null)
  const loading = ref(false)
  
  async function fetch() {
    loading.value = true
    user.value = await api.getUser(userId)
    loading.value = false
  }
  
  async function update(data) {
    await api.updateUser(userId, data)
    await fetch()
  }
  
  onMounted(fetch)
  
  return { user, loading, update }
}

// usePosts.js
export function usePosts(userId) {
  const posts = ref([])
  const loading = ref(false)
  
  async function fetch() {
    loading.value = true
    posts.value = await api.getPosts(userId)
    loading.value = false
  }
  
  async function create(post) {
    await api.createPost(userId, post)
    await fetch()
  }
  
  onMounted(fetch)
  
  return { posts, loading, create }
}

// UserProfile.vue
export default {
  setup() {
    const userId = route.params.id
    
    const userProfile = useUserProfile(userId)
    const userPosts = usePosts(userId)
    
    return {
      ...userProfile,
      ...userPosts
    }
  }
}

Each composition function is:

  • Focused on one feature
  • Testable in isolation
  • Reusable across components

Reactivity

ref

For primitive values:

const count = ref(0)
console.log(count.value)  // 0
count.value++
console.log(count.value)  // 1

reactive

For objects:

const state = reactive({
  count: 0,
  name: 'John'
})

console.log(state.count)  // 0 (no .value needed)
state.count++

computed

const count = ref(0)
const doubled = computed(() => count.value * 2)

console.log(doubled.value)  // 0
count.value = 5
console.log(doubled.value)  // 10

watch

const count = ref(0)

watch(count, (newValue, oldValue) => {
  console.log(`Count changed from ${oldValue} to ${newValue}`)
})

count.value++  // Logs: "Count changed from 0 to 1"

Lifecycle Hooks

import { onMounted, onUpdated, onUnmounted } from 'vue'

export default {
  setup() {
    onMounted(() => {
      console.log('mounted')
    })
    
    onUpdated(() => {
      console.log('updated')
    })
    
    onUnmounted(() => {
      console.log('unmounted')
    })
  }
}

TypeScript Support

Composition API has better TypeScript support:

import { ref, Ref } from 'vue'

interface User {
  id: number
  name: string
}

export function useUser(): {
  user: Ref<User | null>
  loading: Ref<boolean>
  fetch: () => Promise<void>
} {
  const user = ref<User | null>(null)
  const loading = ref(false)
  
  async function fetch() {
    loading.value = true
    user.value = await api.getUser()
    loading.value = false
  }
  
  return { user, loading, fetch }
}

Full type inference!

Comparison with React Hooks

Similar concept, different implementation:

React Hooks:

  • Must follow rules (no conditionals, no loops)
  • Re-run on every render
  • Dependency arrays

Vue Composition API:

  • No special rules
  • Run once in setup()
  • Automatic dependency tracking

Should You Use It?

Yes, if:

  • Building large components
  • Want better code organization
  • Need TypeScript support
  • Want to share logic between components

Stick with Options API if:

  • Building small components
  • Team prefers Options API
  • Don’t have complex logic

Both APIs will be supported. You can mix them.

When Will It Be Available?

  • Vue 2.x: Plugin available now (@vue/composition-api)
  • Vue 3.0: Built-in (coming Q1 2020)

Our Plan

  • Try it in new components
  • Refactor complex components gradually
  • Create composition function library
  • Wait for Vue 3.0 for full adoption

The Verdict

Composition API solves real problems in large Vue apps. Better code organization, better reusability, better TypeScript support.

It’s not replacing Options API - it’s an addition. Use what works for your use case.

I’m excited about this. It’s going to make Vue even better.

Questions? Let me know!