0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vue.js 循環依存エラーを依存性注入(DI)で解決する

0
Posted at

Vueの開発業務で、循環依存エラーの解決の際に依存性注入を知ったのと、プログラミングでよく出てくる 「依存」 をこの機会に調べたので、記事にまとめました。

そもそも「依存」とは?

依存(Dependency) とは、あるモジュール(ファイル・関数・クラス)が他のモジュールの機能を使っている(依存している)状態のこと。

自動車
 ↓ 使う(依存)
エンジン
 ↓ 使う(依存)
ガソリン

依存 あるモジュールが他のモジュールの機能を使っている状態
依存関係 どのモジュールがどのモジュールに依存しているかの関係
依存する側 機能を使う側(import する側)
依存される側 機能を提供する側(import される側)
直接依存 直接 import して使う
間接依存 他のモジュールを経由して間接的に使う
循環依存 お互いに依存し合っている(本記事のテーマ)
密結合 依存が強い(変更の影響が大きい)
疎結合 依存が弱い(変更の影響が小さい)

依存関係は同一モジュール内でも発生します。
例:関数間の依存

// utils.tsに以下2つの関数が定義されている

const calculateTax = (price: number): number => {
  return price * 0.1
}

const calculateTotal = (price: number, quantity: number): number => {
  const subtotal = price * quantity
  const tax = calculateTax(subtotal) // ← calculateTax に依存
  return subtotal + tax
}
  • calculateTotalは依存する側で、calculateTaxは依存される側
  • calculateTotal は calculateTax に依存している
  • calculateTax がないと calculateTotal は動かない
  • calculateTax の仕様が変わると calculateTotal に影響する

循環依存とは

2つ以上のモジュール(ファイル)がお互いに依存し合っている状態

分かりやすく言い換えると、2つ以上のモジュール(ファイル)がお互いにインポート(import)し合うことで発生するエラー。

スクリーンショット 2026-02-23 15.53.21.png
両方が相手を必要としているため、どちらを先に初期化すればいいか分からず、エラーになります。

ストアとフックに限らす、フック同士でも起きる
*コンポーネント同士でもおきますが、コンポーネントを相互にインポートしあう事はあまりないかと思います。

循環依存エラーが起きるケース

// Store
import { useDoubleCounter } from '@/composables/useDoubleCounter' // ❌ hooksをインポート

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  
  increment = () => {
    count.value++
  }
  
  // ❌ hooksを使用
  incrementDouble = () => {
    const { doubleAndIncrement } = useDoubleCounter()
    doubleAndIncrement()
  }
  
  return { 
    count, 
    increment,
    incrementDouble 
  }
})
// hooks
import { useCounterStore } from '@/stores/counter' // ❌ Storeをインポート

export function useDoubleCounter() {
  doubleAndIncrement = () => {
    const store = useCounterStore() // ❌ Storeを使用
    
    // カウントを2倍にしてからインクリメント
    store.count = store.count * 2
    store.increment() // ❌ Storeのメソッドを使用
  }
  
  return { doubleAndIncrement }
}

Store → Hooks → Store の相互依存で初期化順序が決まらないのが原因。

依存性注入(Dependency Injection, DI)とは

オブジェクトやモジュールが必要とする依存関係を、外部から注入(渡す)する設計パターン

注入は依存関係を外部から渡すこと全般を指します。

// 関数を引数で渡す
useValidation = (updateFn: (value: string) => void) => {
  updateFn('test') // 注入された関数を使用
}
useValidation((value) => console.log(value)) // 関数を注入
// コンストラクタで渡す
class UserService {
  constructor(private repository: UserRepository) {}
}

const service = new UserService(new UserRepository()) 

つまりhooksの内部で必要なメソッドをストアからインポートするのではなく、ストアから注入してもらう

スクリーンショット 2026-02-23 15.53.26.png

依存性注入により循環依存エラーを解決

前述の循環依存エラーを依存性注入で解決するサンプル

// Store
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  
  increment = () => {
    count.value++
  }
  
  // ✅ hooksを初期化する際に必要なメソッド(依存)を注入
  incrementDouble = () => {
    const { doubleAndIncrement } = useDoubleCounter({
      count: count.value,
      increment
    })
    doubleAndIncrement()
  }
  
  return { 
    count, 
    increment,
    incrementDouble 
  }
})
// hooks
// ❌ import { useCounterStore } from '@/stores/counter'
// ✅ Storeをインポートしない

interface DoubleCounterDeps {
  count: number
  increment: () => void
}

export function useDoubleCounter(deps?: DoubleCounterDeps) {
  doubleAndIncrement = () => {
    if (!deps) return
    
    // ✅ 注入された値とメソッドを使用するので、Storeをインポートしなくていい
    const doubled = deps.count * 2
    console.log(`${deps.count}${doubled}`)
    deps.increment()
  }
  
  return { doubleAndIncrement }
}

ポイント:

  • hooksはStoreをimportしない
  • Storeだけがhooksをimport
  • Storeがhooksに必要なメソッドを渡す

これにより、一方向の依存になり、初期化順序が明確

スクリーンショット 2026-02-23 15.53.26.png

*技術的には「コールバック関数」でもあるが、目的は「循環依存の解決」なので「依存性注入」と呼ぶ。

DI以外の解決策

Storeのメソッドを別のhooksに分離が可能であれば、Store側で両方のhooksをimportするのも手です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?