# Provide / inject

このページは既にコンポーネントの基本を読んでいる事を前提としています。 コンポーネントを初めて使う方はそちらを先にお読みください。

通常、親コンポーネントから子コンポーネントにデータを渡すとき、props を使います。深くネストされたいくつかのコンポーネントがあり、深い階層にあるコンポーネントが浅い階層にあるコンポーネントの何かしらのデータのみを必要としている構造を想像してください。この場合でも、鎖のように繋ったコンポーネント全体にプロパティを渡す必要がありますが、時にそれは面倒になります。

そのような場合は、provideinject のペアを利用できます。コンポーネント階層の深さに関係なく、親コンポーネントは、そのすべての子階層へ依存関係を提供するプロバイダとして機能することができます。この機能は 2 つの機能からなります: 親コンポーネントは、データを提供するためのオプション provide を持ち、子コンポーネントはそのデータを利用するためのオプション inject を持っています。

Provide/inject scheme

例えば、このような構造があるとします:

Root
└─ TodoList
   ├─ TodoItem
   └─ TodoListFooter
      ├─ ClearTodosButton
      └─ TodoListStatistics
1
2
3
4
5
6

もし todo-items のサイズを TodoListStatistics に渡したい場合、プロパティをこのように渡します: TodoListTodoListFooterTodoListStatistics。provide/inject を利用すると、これを直接的に行えます。

const app = Vue.createApp({})

app.component('todo-list', {
  data() {
    return {
      todos: ['Feed a cat', 'Buy tickets']
    }
  },
  provide: {
    user: 'John Doe'
  },
  template: `
    <div>
      {{ todos.length }}
      <!-- rest of the template -->
    </div>
  `
})

app.component('todo-list-statistics', {
  inject: ['user'],
  created() {
    console.log(`Injected property: ${this.user}`) // > Injected property: John Doe
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

ただし、ここでコンポーネントのインスタンスプロパティを提供しようとしても、うまく動作しないでしょう:

app.component('todo-list', {
  data() {
    return {
      todos: ['Feed a cat', 'Buy tickets']
    }
  },
  provide: {
    todoLength: this.todos.length // this will result in error `Cannot read property 'length' of undefined`
  },
  template: `
    ...
  `
})
1
2
3
4
5
6
7
8
9
10
11
12
13

コンポーネントのインスタンスプロパティにアクセスするためには、provide をオブジェクトを返す関数へ変換する必要があります

app.component('todo-list', {
  data() {
    return {
      todos: ['Feed a cat', 'Buy tickets']
    }
  },
  provide() {
    return {
      todoLength: this.todos.length
    }
  },
  template: `
    ...
  `
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

こうすることで、子コンポーネントが依存している何かを変更したり削除したりしてしまうことを恐れることなく、より安全にコンポーネントを開発し続けることができます。これらのコンポーネント間のインターフェースは、props と同じく、明確に定義されています。

実際、依存関係の注入は、いわば「長距離な props」のように考えることができます。後述の点を除きます:

  • 親コンポーネントは、提供したプロパティを子孫コンポーネントのどこで使用しているか知る必要がありません
  • 子コンポーネントは、注入されたプロパティがどこから提供されたものなのか知る必要がありません

# リアクティブと連携する

前述の例では、リスト todos を変更しても、その変更は注入された todoLength には反映されません。これは、provide/inject の束縛(binding)がデフォルトでリアクティブ でない ことが原因です。ref で定義されたプロパティや reactive で作成されたオブジェクトを provide に渡すことにより、この振る舞いを変更することができます。この場合、祖先コンポーネントをリアクティブにするためには、Composition API の computed で定義したプロパティ todoLength を割り当てる必要があります。

app.component('todo-list', {
  // ...
  provide() {
    return {
      todoLength: Vue.computed(() => this.todos.length)
    }
  }
})

app.component('todo-list-statistics', {
  inject: ['todoLength'],
  created() {
    console.log(`Injected property: ${this.todoLength.value}`) // > Injected property: 5
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

こうすると、todos.length へのあらゆる変更は、todoLength が注入されたコンポーネントに正しく反映されます。computed については、 算出プロパティとウォッチのセクション を、そして reactive の provide/inject の詳細については、Composition API のセクション をご覧ください。