# はじめに
# なぜ Composition API なのか?
Note
ドキュメントでここまで到達しているならば、Vue の基本とコンポーネントの作成の両方にすでに精通しているはずです。
Vue コンポーネントを作成するとその機能と結合されたインターフェースの繰り返し可能な部分を再利用可能なコードとして抽出することができます。これだけでも保守性と柔軟性の点でアプリケーションをかなり良くすることができます。しかし、蓄積された経験から、特にアプリケーションが非常に大規模な場合は、これだけでは不十分であることがわかっています。数百のコンポーネントがある場合を想像してみてください。そのような大規模なアプリケーションを扱う場合は、コードを共通化して再利用することが非常に重要になります。
このアプリには、特定のユーザーのリポジトリのリストを表示するビューがあると想像してみましょう。さらに、検索と絞り込みの機能を実装したいとします。このビューを処理するコンポーネントは次のようになります:
// src/components/UserRepositories.vue
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
data () {
return {
repositories: [], // 1
filters: { ... }, // 3
searchQuery: '' // 2
}
},
computed: {
filteredRepositories () { ... }, // 3
repositoriesMatchingSearchQuery () { ... }, // 2
},
watch: {
user: 'getUserRepositories' // 1
},
methods: {
getUserRepositories () {
// `this.user` を使用してユーザーのリポジトリを取得します
}, // 1
updateFilters () { ... }, // 3
},
mounted () {
this.getUserRepositories() // 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
26
27
28
29
30
31
32
33
34
このコンポーネントには複数の責務が存在します:
- 適当な外部 API からユーザー名に対応したリポジトリを取得して、ユーザーが変化するたびにそれを更新する
searchQuery
文字列を使用してリポジトリを検索するfilters
オブジェクトを使用してリポジトリを絞り込む
コンポーネントのオプション (data
, computed
, methods
, watch
) でまとめたロジックはたいていの場合は正しく動作します。しかし、コンポーネントがより大きくなれば、論理的な関心事のリストもまた大きくなります。これは、特に最初からコンポーネントを書いていない人々にとって、コンポーネントを読みづらく、理解しづらいものにするかもしれません。
論理的な関心事 が色でグループ化されている大きなコンポーネントを示す例。
このような分離は、複雑なコンポーネントを理解してメンテナンスすることを難しくします。このオプションの分離は背景にある論理的な関心事をわかりづらくします。さらに、単一の論理的な関心事に取り組む場合、関連するコードのオプションブロックを何度も "ジャンプ" する必要があります。
同じ論理的な関心事に関連するコードを並べることができれば、より良くなるでしょう。そして、これはまさに Composition API が可能にします。
# Composition API の基本
これでなぜこの方法にたどり着くのかわかりました。 Composition API の使用を開始するには、初めに実際に使用できる場所が必要です。Vue コンポーネントでは、この場所を setup
と呼びます。
# setup
コンポーネントオプション
新しい setup
コンポーネントオプションは、コンポーネントが作成される前に props
が解決されると実行され、 Composition API のエントリポイントとして機能します。
WARNING
setup
が実行されたときは、まだコンポーネントのインスタンスが作られないため、setup
オプションの中では this
を使用できません。これは props
を除いて、コンポーネント内で宣言されているあらゆるプロパティ (ローカルの state や computed プロパティ、methods) にアクセスできないことを意味します。
setup
オプションは props
と後で紹介する context
を受け付ける関数であるべきです。さらに、setup
から返される全てのものは、コンポーネントの残りの要素 (computed プロパティ、methods、ライフサイクルフックなど) およびコンポーネントの template に公開されます。
setup
をコンポーネントに追加しましょう:
// src/components/UserRepositories.vue
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
console.log(props) // { user: '' }
return {} // ここで返されるものはコンポーネントの他のオプションで使用可能です
}
// 以降、コンポーネントの"他"のオプション
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
それでは、最初の論理的な関心事 (元のスニペットで "1" とマークされている) を抽出することから始めましょう。
- 適当な外部 API からユーザー名に対応したリポジトリを取得して、ユーザーが変化するたびにそれを更新する
最も明らかな部分から始めましょう:
- リポジトリのリスト
- リポジトリのリストを更新する関数
- リストと関数を返して、他のコンポーネントのオプションから利用できるようにすること
// src/components/UserRepositories.vue `setup` 関数
import { fetchUserRepositories } from '@/api/repositories'
// コンポーネント内部
setup (props) {
let repositories = []
const getUserRepositories = async () => {
repositories = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories // 返される関数は methods と同様の振る舞いをします
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
これが出発点ですが、repositories
変数がリアクティブではないのでまだ正しく動作しません。これはユーザーの観点からは、リポジトリのリストは空のままになっていることを意味します。これを直しましょう!
# ref
によるリアクティブな変数
Vue 3.0 では、このように新しい ref
関数にとってあらゆる変数をリアクティブにすることができます:
import { ref } from 'vue'
const counter = ref(0)
2
3
ref
は引数を受け取って、それを value
プロパティを持つにオブジェクトでラップして返します。これを利用して、リアクティブな変数の値にアクセスしたり、変更したりします。
import { ref } from 'vue'
const counter = ref(0)
console.log(counter) // { value: 0 }
console.log(counter.value) // 0
counter.value++
console.log(counter.value) // 1
2
3
4
5
6
7
8
9
オブジェクト内で値をラップすることは不要に思うかもしれませんが、JavaScript の異なるデータ型の間で動作を統一するために必要です。JavaScript において Number
や String
のようなプリミティブな型は参照ではなく、値によって渡されるからです:
どんな値でもラッパーオブジェクトを持つことで、途中でリアクティブでなくなることがなく、アプリ全体に安全に渡すことができます。
Note
言い換えれば、ref
は値へのリアクティブな参照を作成します。 参照を操作するという概念は Composition API 全体で頻繁に使用されます。
例に戻って、リアクティブな repositories
変数を作成しましょう:
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'
// コンポーネント内部
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
完了です!これで getUserRepositories
を呼ぶ出すたびに、repositories
が更新され、変更を反映するようにビューが更新されます。コンポーネントは次のようになります:
// src/components/UserRepositories.vue
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories
}
},
data () {
return {
filters: { ... }, // 3
searchQuery: '' // 2
}
},
computed: {
filteredRepositories () { ... }, // 3
repositoriesMatchingSearchQuery () { ... }, // 2
},
watch: {
user: 'getUserRepositories' // 1
},
methods: {
updateFilters () { ... }, // 3
},
mounted () {
this.getUserRepositories() // 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
最初の論理的な関心事のいくつかを setup
メソッドに移動し、お互いにうまく近づけました。残りは mounted
フックで getUserRepositories
を呼び出し、user
プロパティが変更されるたびに、それを実行するようにウォッチャを設定することです。
ライフサイクルフックから始めます。
# ライフサイクルフックを setup
の中に登録する
オプション API に比べて Composition API の機能を完全にするには、ライフサイクルフックを setup
の中に登録する必要があります。これは Vue から提供されるいくつかの新しい関数のおかげで可能になりました。 Composition API におけるライフサイクルフックはオプション API と同様の名称ですが、on
というプレフィックスが付いています。例: mounted
は onMounted
のようになっています。
それらの関数はコンポーネントによってフックが呼び出されるときに実行されるコールバックを受け付けます。
では、setup
関数にそれを追加してみましょう:
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'
// コンポーネント内部
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
onMounted(getUserRepositories) // `mounted` が `getUserRepositories` を呼び出します
return {
repositories,
getUserRepositories
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
これで user
プロパティへの変更に反応する必要があります。そのために、スタンドアロンの watch
関数を使用します。
# watch
で変化に反応する
コンポーネント内の user
プロパティに watch
オプションを使用してウォッチャを追加するのと同じように、Vue からインポートした watch
関数を使用することができます。それは以下の 3 つの引数を受け付けます:
- リアクティブな参照または監視するゲッター関数
- コールバック
- 任意の設定オプション
その仕組みをざっと見てみましょう。
import { ref, watch } from 'vue'
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
2
3
4
5
6
counter.value = 5
のようにcounter
が更新されたときは、ウォッチはコールバック (第 2 引数) をトリガーして実行します。この場合は、コンソールに 'The new counter value is: 5'
を出力します。
以下はオプション API と同様です:
export default {
data() {
return {
counter: 0
}
},
watch: {
counter(newValue, oldValue) {
console.log('The new counter value is: ' + this.counter)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
watch
についての詳細は、詳細ガイド を参照してください。
例に適用しましょう:
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'
// コンポーネント内部
setup (props) {
// props の `user` プロパティへのリアクティブな参照を作成するために `toRefs` を使用します
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
// リアクティブな値にアクセスするために `props.user` を `user.value` に更新します
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
// ユーザープロパティへのリアクティブな参照のウォッチャをセットします
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
おそらく setup
の先頭で toRefs
が使われていることにお気づきでしょう。これはウォッチャが user
プロパティに加えられた変更に確実に反応するためです。
これらの変更が行われたので、最初の論理的な関心事全体を 1 つの場所に移動しました。これで、2 番目の懸念事項である searchQuery
に基づいた絞り込みでも同じことができます。今回は computed プロパティを使用します。
# スタンドアロンな computed
プロパティ
ref
や watch
に似て、computed プロパティもまた Vue からインポートされた computed
関数を使用して Vue コンポーネントの外部でも作成することができます。カウンターの例に戻りましょう:
import { ref, computed } from 'vue'
const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2
2
3
4
5
6
7
8
ここで、computed
関数は computed
の第 1 引数として渡されたゲッターのようなコールバックの出力として読み取り専用のリアクティブな参照を返します。新しく作成され算出された変数の値にアクセスするためには、ref
と同様に .value
プロパティを使う必要があります。
検索機能を setup
に移動させましょう:
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'
// コンポーネント内部
setup (props) {
// props の `user` プロパティへのリアクティブな参照を作成するために `toRefs` を使用します
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
// リアクティブな値にアクセスするために `props.user` を `user.value` に更新します
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
// ユーザープロパティへのリアクティブな参照のウォッチャをセットします
watch(user, getUserRepositories)
const searchQuery = ref('')
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(
repository => repository.name.includes(searchQuery.value)
)
})
return {
repositories,
getUserRepositories,
searchQuery,
repositoriesMatchingSearchQuery
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
他の論理的な関心事にも同じことができますが、既に疑問があるかもしれません。- これはコードを setup
オプションに移動させて肥大させるだけではありませんか? そのため他の責務に移る前に、まず上記のコードをスタンドアロンなComposition 関数に抽出します。 useUserRepositories
の作成から始めましょう:
// src/composables/useUserRepositories.js
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch } from 'vue'
export default function useUserRepositories(user) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
それから検索機能も:
// src/composables/useRepositoryNameSearch.js
import { ref, computed } from 'vue'
export default function useRepositoryNameSearch(repositories) {
const searchQuery = ref('')
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(repository => {
return repository.name.includes(searchQuery.value)
})
})
return {
searchQuery,
repositoriesMatchingSearchQuery
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
これらの 2 つの機能が別々のファイルにあるので、コンポーネントでそれらを使い始められます。これを行う方法は次の通りです:
// src/components/UserRepositories.vue
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import { toRefs } from 'vue'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup (props) {
const { user } = toRefs(props)
const { repositories, getUserRepositories } = useUserRepositories(user)
const {
searchQuery,
repositoriesMatchingSearchQuery
} = useRepositoryNameSearch(repositories)
return {
// 絞り込まれていないリポジトリはあまり考慮しないので、
// 絞り込んだ結果を `repositories` という名前で返して良いでしょう
repositories: repositoriesMatchingSearchQuery,
getUserRepositories,
searchQuery,
}
},
data () {
return {
filters: { ... }, // 3
}
},
computed: {
filteredRepositories () { ... }, // 3
},
methods: {
updateFilters () { ... }, // 3
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
この時点でおそらくあなたは既にノウハウを知っているので、最後まで飛ばして、残りの絞り込み機能を移行しましょう。このガイドの目的ではないため、実装の詳細には立ち入りません。
// src/components/UserRepositories.vue
import { toRefs } from 'vue'
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import useRepositoryFilters from '@/composables/useRepositoryFilters'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
const { user } = toRefs(props)
const { repositories, getUserRepositories } = useUserRepositories(user)
const {
searchQuery,
repositoriesMatchingSearchQuery
} = useRepositoryNameSearch(repositories)
const {
filters,
updateFilters,
filteredRepositories
} = useRepositoryFilters(repositoriesMatchingSearchQuery)
return {
// 絞り込まれていないリポジトリはあまり考慮しないので、
// 絞り込んだ結果を `repositories` という名前で返して良いでしょう
repositories: filteredRepositories,
getUserRepositories,
searchQuery,
filters,
updateFilters
}
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
これで完了です!
Composition API の表面とできることについてほんの少し触れただけであることを覚えておいてください。より詳しく知りたい場合は、詳細ガイドを参照してください。
← 状態のトランジション セットアップ →