Tasuke Hubのロゴ

ITを中心に困っている人を助けるメディア

分かりやすく解決策を提供することで、あなたの困ったをサポート。 全ての人々がスムーズに生活できる世界を目指します。

Vue.js 3のComposition API完全ガイド!Reactユーザーでも5分で理解できる実践的な使い方

記事のサムネイル

Vue.js 3のComposition APIとは

Vue.js 3で導入されたComposition APIは、コンポーネントのロジックをより柔軟に記述できる新しいAPIです。従来のOptions API(data、methods、computedなどを分けて書く方式)とは異なり、すべてのロジックをsetup関数内にまとめて記述できます。

// Options API(従来の書き方)
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
// Composition API(新しい書き方)
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const increment = () => count.value++
    
    return {
      count,
      increment
    }
  }
}

最大のメリットは、関連する機能をまとめて書けることです。大きなコンポーネントでも、ユーザー管理、商品管理などの機能ごとにコードをグループ化できるため、保守性が格段に向上します。

ReactのHooksとの違いと共通点

Reactの経験がある方なら、Composition APIはHooksにとても似ていることに気づくでしょう。どちらもコンポーネントのロジックを関数ベースで書く仕組みです。

// React Hooks
import { useState, useEffect } from 'react'

function Counter() {
  const [count, setCount] = useState(0)
  
  useEffect(() => {
    document.title = `Count: ${count}`
  }, [count])
  
  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  )
}
// Vue Composition API
import { ref, watchEffect } from 'vue'

export default {
  setup() {
    const count = ref(0)
    
    watchEffect(() => {
      document.title = `Count: ${count.value}`
    })
    
    return {
      count,
      increment: () => count.value++
    }
  }
}

主な違いは以下の通りです:

  • リアクティビティ: Vueはrefやreactiveでリアクティブなデータを明示的に作成します
  • 再実行の仕組み: ReactのHooksは再レンダー時に毎回実行されますが、VueのComposition APIは一度だけ実行されます
  • 依存配列: Reactのように依存配列を手動で管理する必要がなく、Vueが自動で追跡してくれます

基本的なsetupの書き方と実装例

setup関数はComposition APIの入り口です。ここでリアクティブなデータや関数を定義し、テンプレートで使用するものをreturnします。

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

export default {
  setup() {
    // リアクティブなデータ
    const message = ref('Hello Vue 3!')
    const todos = ref([])
    
    // 算出プロパティ
    const todoCount = computed(() => todos.value.length)
    
    // メソッド
    const addTodo = (text) => {
      todos.value.push({
        id: Date.now(),
        text,
        completed: false
      })
    }
    
    // ライフサイクルフック
    onMounted(() => {
      console.log('コンポーネントがマウントされました')
    })
    
    // テンプレートで使用するものを返す
    return {
      message,
      todos,
      todoCount,
      addTodo
    }
  }
}

重要なポイントは、refで作成したリアクティブな値にアクセスする際は.valueを使うことです。テンプレート内では自動的にアンラップされるので.valueは不要ですが、JavaScript内では必須です。

よく使うComposition API関数の実践例

実際の開発でよく使用されるComposition API関数とその使い方を紹介します。

ref vs reactive

プリミティブな値にはref、オブジェクトにはreactiveを使います:

import { ref, reactive } from 'vue'

export default {
  setup() {
    // プリミティブ値にはref
    const count = ref(0)
    const isLoading = ref(false)
    
    // オブジェクトにはreactive
    const user = reactive({
      name: '田中太郎',
      age: 25,
      email: '[email protected]'
    })
    
    return { count, isLoading, user }
  }
}

watch と watchEffect

データの変更を監視する2つの方法があります:

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

export default {
  setup() {
    const searchQuery = ref('')
    const results = ref([])
    
    // 特定の値を監視(React useEffectの依存配列に近い)
    watch(searchQuery, async (newQuery) => {
      if (newQuery) {
        results.value = await searchAPI(newQuery)
      }
    })
    
    // 自動的に依存関係を追跡
    watchEffect(() => {
      // この中で使用したリアクティブな値が変更されると自動実行
      console.log(`検索キーワード: ${searchQuery.value}`)
    })
    
    return { searchQuery, results }
  }
}

カスタムフック(composables)

ロジックを再利用可能な関数として切り出せます:

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

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

// コンポーネント内で使用
import { useCounter } from './useCounter'

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

既存のOptions APIからの移行方法

既存のOptions APIコンポーネントをComposition APIに移行する際のパターンを理解しておきましょう。幸い、両者は同じコンポーネント内で併用できるので、段階的な移行が可能です。

基本的な変換パターン

// Options API(移行前)
export default {
  data() {
    return {
      name: '',
      email: '',
      isSubmitting: false
    }
  },
  computed: {
    isValid() {
      return this.name && this.email
    }
  },
  methods: {
    async submit() {
      this.isSubmitting = true
      try {
        await this.saveUser()
      } finally {
        this.isSubmitting = false
      }
    },
    async saveUser() {
      // API呼び出し
    }
  },
  mounted() {
    this.loadUserData()
  }
}
// Composition API(移行後)
import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    const name = ref('')
    const email = ref('')
    const isSubmitting = ref(false)
    
    const isValid = computed(() => name.value && email.value)
    
    const saveUser = async () => {
      // API呼び出し
    }
    
    const submit = async () => {
      isSubmitting.value = true
      try {
        await saveUser()
      } finally {
        isSubmitting.value = false
      }
    }
    
    const loadUserData = () => {
      // ユーザーデータの読み込み
    }
    
    onMounted(() => {
      loadUserData()
    })
    
    return {
      name,
      email,
      isSubmitting,
      isValid,
      submit
    }
  }
}

段階的移行のコツ

一度にすべてを移行する必要はありません。新しい機能はComposition APIで書き、既存部分は必要に応じて移行していくのが効率的です:

export default {
  // 既存のOptions API
  data() {
    return {
      oldFeature: 'これは残しておく'
    }
  },
  
  // 新しいComposition API
  setup() {
    const newFeature = ref('新機能はこちらで')
    
    return {
      newFeature
    }
  }
}

トラブルシューティングとベストプラクティス

Composition APIを使用する際によく遭遇する問題と解決策をまとめました。

よくあるエラーと解決法

  1. 「Cannot read property 'value' of undefined」エラー
// ❌ NGパターン
export default {
  setup() {
    let count // refを使っていない
    
    const increment = () => {
      count.value++ // エラー:countはundefined
    }
    
    return { count, increment }
  }
}

// ✅ OKパターン
export default {
  setup() {
    const count = ref(0) // refで作成
    
    const increment = () => {
      count.value++
    }
    
    return { count, increment }
  }
}
  1. リアクティビティが効かない問題
// ❌ NGパターン
const user = reactive({
  name: '田中太郎'
})

// オブジェクトを分割代入すると reactivity が失われる
const { name } = user
name = '佐藤花子' // リアクティビティが効かない

// ✅ OKパターン
const user = reactive({
  name: '田中太郎'
})

// toRefsを使って分割代入
const { name } = toRefs(user)
name.value = '佐藤花子' // 正しく動作

ベストプラクティス

  1. 関数の命名規則を統一する

composable関数は use で始める慣習があります:

// ✅ 良い例
export function useUserAuth() { /* ... */ }
export function useLocalStorage() { /* ... */ }

// ❌ 避けたい例
export function authManager() { /* ... */ }
export function storage() { /* ... */ }
  1. setup関数内でのthis使用を避ける
// ❌ NGパターン
export default {
  setup() {
    const doSomething = () => {
      console.log(this) // undefinedになる
    }
  }
}

// ✅ OKパターン
export default {
  setup(props, { emit }) {
    const doSomething = () => {
      emit('custom-event') // 正しい方法
    }
  }
}
  1. メモリリークを避ける
export default {
  setup() {
    const timer = ref(null)
    
    onMounted(() => {
      timer.value = setInterval(() => {
        console.log('定期実行')
      }, 1000)
    })
    
    // 重要:コンポーネント破棄時にクリーンアップ
    onBeforeUnmount(() => {
      if (timer.value) {
        clearInterval(timer.value)
      }
    })
    
    return {}
  }
}

これらのポイントを押さえることで、Composition APIを安全かつ効率的に活用できます。Vue.js 3の新機能を存分に活用して、よりメンテナンスしやすいコードを書いていきましょう!

TH

Tasuke Hub管理人

東証プライム市場上場企業エンジニア

情報系修士卒業後、大手IT企業にてフルスタックエンジニアとして活躍。 Webアプリケーション開発からクラウドインフラ構築まで幅広い技術に精通し、 複数のプロジェクトでリードエンジニアを担当。 技術ブログやオープンソースへの貢献を通じて、日本のIT技術コミュニティに積極的に関わっている。

🎓情報系修士🏢東証プライム上場企業💻フルスタックエンジニア📝技術ブログ執筆者

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

おすすめ記事

おすすめコンテンツ