この記事をおすすめしたい人
- Nuxt v3に手を出したら意味不明になった人
- script setupのせいで何がどこにあるのか分からなくなった人
- つまりオレ
今回はNuxt v3移植シリーズのdefineNuxtComponent編です。
Nuxt v3に移植しようとして
このscript setupの意味が分かんねんだよ!!
ってなった人はいないですか?いますね。
そう、僕です。
v3はv3になったこと以外にも、TypeScriptやこのscript setupなどの新しい要素が増えていて、v2での環境次第ではおそろしく学習コストが高くなるので、まずはなるべくv2と似た感じでお勉強していこうと思います。
そのための書き方がdefineNuxtComponent。 僕みたいな阿呆にでもv3を体験できるよう、かなりv2に近い書き方ができるようになっていました。
このページの目次
defineNuxtComponentの書き方
defineNuxtComponentはこの↓ようにかなりv2に近い書き方ができます。
export default defineNuxtComponent({
data() {
return {
dataTest: 'data-test',
}
},
computed: {
computedTest() {
return 'computed-test'
},
},
})
ここまでだとTypeScriptの型が付いているくらいしか違いがありません。
書き方以外にも、script setupだとただの命令文の連続なので先に書いたものしか使えませんでしたが、defineNuxtComponentだとこの↓ように、後に書いたものを参照することもできます(v2と同じ)。
export default defineNuxtComponent({
computed: {
SakiNiExe() {
return this.computedTest
},
computedTest() {
return 'computed-test'
},
},
})
ほな皆これでええやん。
というわけでもなく、公式はscript setupの方を推奨と書いているので、v4で消えるのか、v3でも途中で見放されるのか、現段階でも何かデメリットがあるのかもしれません。
公式の説明では、VueのdefineComponentをベースに、asyncDataとheadを拡張したのがdefineNuxtComponent…たぶん。 v2で触ったことのある各項目について調べていきます。
thisが今まで通りに使えない!
v2だとこんな↓コードで、現在のページのパスが返ります。
export default defineNuxtComponent({
computed: {
computedTest() {
return this.$route.path
},
},
})
ところがv3では少なくとも$routeは無理っぽい。
じゃーどうすればいいかというと、こんな↓風にuseRouteを使うみたいです。
export default defineNuxtComponent({
computed: {
computedTest() {
const route = useRoute()
return route.path
},
},
})
こうやるとrouteの中にv2のthis.$routeの中身が入ります。
そのページ内で何度も使い回す場合は、こんな↓風にcomputedにしてしまうとv2とかなり近い感じで使えます。
export default defineNuxtComponent({
computed: {
computedTest() {
return this.route.path
},
route() {
return useRoute()
},
},
})
v3の「v2のアレはどうやってアクセスするのか分からない!」ってのはこういうuseナントカ系(composablesと呼ぶらしい)でけっこういけるので、この書き方を覚えてればどうにかなりそう。 公式のこのページの左メニューの「Composables」というところに一覧があります。
これ以外の自分で作ったdataとかcomputedは従来と同じようにthisでアクセスできるようでした。
注意点として、export defaultの外側でこの関数を呼ぶとエラーが出ます。 呼び出し側に「this」がいるかどうかとかじゃなくてコールした段階でエラーが出てます。
const route = useRoute()
export default defineNuxtComponent({
computed: {
computedTest() {
return this.route.path
},
},
})
おそらくscriptではexport defaultの外側はインスタンスができる前に呼ばれていて、script setupではインスタンスができた後に呼ばれているせいじゃないかと思います。
dataはそのまま使えそう
コンポーネント内で使い回す変数を定義するdataの部分はv2と同じ書き方↓でいけました。
export default defineNuxtComponent({
data() {
return {
key: value,
}
},
})
さらにおまけに、script setupの書き方だとリアクティビティとかいうのを考えないといけないっぽくて、templateから変数の変化を検知するような仕組み(アコーディオンの開閉とか)の場合、この↓ようにrefを使って初期化しないといけません。
const flag = ref(true)
script内では「flag.value」に値が入っていたり、templateでは「flag」のままいけたりとなんか気持ち悪かったんですが、defineNuxtComponentのdataだとv2と同じように「flag」でscriptからもtemplateからもアクセスできます。
computedやmethodsも同じ
computedは計算付き変数?、methodsは関数を定義する部分です。 書き方においてはv2と全く同じだと思います。
export default defineNuxtComponent({
computed: {
computedTest() {
return 'computed-test'
},
},
methods: {
methodsTest(v) {
return 'methods-test'
},
},
})
mountedは従来通りいけそう?
僕の認識ではmountedはインスタンス完成後、ページ表示前に実行される処理と認識していて、コンストラクタみたいなもん? インスタンスが出来上がっているのでコンポーネントに必要な値は全て揃っているのでその最終調整とかに使っていました。
export default defineNuxtComponent({
mounted() {
console.log('mounted-test')
},
})
とりあえず書き方はv2と同じようで、上の書き方でちゃんと出力されました。
実際の挙動として変化があるのかないのかはまだ分からないので、移植するプロジェクトに使われていたらテストします。 とりあえずuse系、computed、methodsなんかにはアクセスできました。
propsもそのまま使える
propsというのは独自コンポーネントを作る際に、親要素から引数を受け取る方法です。
<MyComponet id="1" />
受け取り側(独自コンポーネント内)はこの↓ようにv2と同じ書き方でv3でも機能しました。
export default defineNuxtComponent({
props: [
'id',
],
})
これでscript内では「this.id」、template内では「id」で親要素からの引数にアクセスできます。
headはけっこう制限あり
defineNuxtComponentにheadを追加すると、v2と同様にheadタグ内の追記?上書き?ができます。
export default defineNuxtComponent({
head(nuxtApp) {
return {
title: 'これはタイトルだよ',
}
},
})
実行順の問題で、アクセスできる要素にけっこうな制限があります。 詳細は後述します。
asyncDataはかなりやっかいそう
asyncDataはコンポーネントのインスタンスが作られる前に実行される部分で、事前に処理して変数を定義したりできる部分です。
実は今回のテストでは移植中のプロジェクトでasyncDataのテストを使っているものがなくて、他の要素を調べ中にまとまってそうだなーと思ったサイトがあったのでメモしておきます。
どうも引数にcontextがなくなったことでいろいろとめんどくさいようです。 実際に試したら追記しておきます。
asyncDataやmountedなどの実行順
defineNuxtComponent内の各項目の実行順とアクセス可能な変数?コンポーサブル?などを確認しました。
僕の中でdefineNuxtComponentの項目は定義系と処理系に分かれていて、あくまで僕の理解なんですが。 定義系は次の3つ。
- props
- computed
- methods
処理系が次の4つ。
- asyncData
- data
- head
- mounted
dataとかasyncDataは定義系とも呼べそうですが、必ず自動で実行されるので処理系の方にいれています。 これら処理系の実行順について調べていきます。 最後にまとめて一覧を表にします。
実行順を調べる
こんな↓コードを書いて、Nuxt3とNuxt2のそれぞれで実行順を調べてみました。
<script>
let num = 0
export default {
head() {
num++
console.log('head: ' + num)
},
asyncData () {
num++
console.log('asyncData: ' + num)
},
data() {
num++
console.log('data: ' + num)
},
mounted() {
num++
console.log('mounted: ' + num)
},
}
</script>
numに加算していくことで実行順や実行回数が分かるはず。
特に注意書きがない限り、Nuxt2ではdataとheadの順序が入れ替わります。 dataが先。
Nuxt3のheadの順番が早すぎ。
初回アクセスとF5リロード
クライアントサイドの実行順↓。
- head: 1
- data: 2
- mounted: 3
サーバサイドの実行順↓。
- asyncData: 1
- data: 2
- head: 3
クライアントサイドではasyncDataが呼ばれず、サーバサイドではmountedが呼ばれません。
また、Nuxt3の初回アクセスではクライアントとサーバの値が連動していて、サーバサイドが後で順に4、5、6となりました。 F5リロードの場合は1、2、3です。
今は意味するところが分からないけど、いずれね…。
ページ移動時
上のnum=3の状態からページ移動で戻ってきた場合の結果↓。
- asyncData: 4
- head: 5
- data: 6
- mounted: 7
クライアントでもサーバでも同じ結果でした。
もう一度ページ移動をした結果↓。
- asyncData: 8
- head: 9
- data: 10
- mounted: 11
初回アクセス時と違って、クライアントサイドでもサーバサイドでもasyncData、mountedともに実行されています。
外側に置いた変数の値がページ移動でも持ち越されるのはちょっと驚きでした。
ホットリロード
ホットリロードと呼ぶのが正しいか分かりませんが、ページ更新に対して自動で再読込が入った時の挙動です。
- asyncData: 1
- head: 2
- data: 3
- mounted: 4
ページ移動と違ってnumが初期化されています。 それ以外は同じ結果。
以上の結果から、アクセスの仕方で多少の違いがあるものの、この↓ような順番で実行されていそうなことが分かります。
- asyncData
- head
- data
- mounted
Nuxt2の場合はこの↓ようにdataとheadの順序が入れ替わります。
- asyncData
- data
- head
- mounted
体感の実行順と完全に一致しました。
headが早いのほんまやめてほしい。
asyncDataからアクセス可能な項目
最速実行でコンポーネントのインスタンスがまだ完成してないので、thisへアクセスできません。 弊害がありそうなのはdata、computed、methodsにアクセスできないことくらい?
dataはasyncDataが返す値そのものがdataと統合されるのであまり意味はないはず。 むしろdataで扱っていたものもここで定義してしまえばいい。
methodsはこんな↓風にdefineNuxtComponentの外側に処理部分を隔離してしまえば、asyncDataからもアクセス可能です。
<script lang="ts">
const func = (value) => {
return value+1
}
export default defineNuxtComponent({
asyncData () {
console.log(func(1))
},
methods: {
plus(value) {
return func(value)
},
},
})
</script>
computedもmethodsと同じく処理部分を外側に置けばどうにかなりそう?
コンポーサブルは自分が使いそうなuseNuxtApp、useAppConfig、useRouteあたりはいけました。 自作のコンポーサブルもアクセス可能。
provide(旧inject)したものはuseNuxtAppの中に入っていました。
propsは無理そう。
headからアクセス可能な項目
不完全ながらthisへアクセスできるようになっています。
アクセス方法が少し違っていて、data、computed、methodsにそれぞれtestという項目があったとすると、以下のようになります。
- data:this.data().test
- computed:this.computed.test
- methods:this.methods.test()
それぞれの項目で、この段階にないthisへのアクセスがあるとエラーが出ます。
コンポーサブルはクライアントサイドではOKで、サーバサイドではダメでした。 process.clientなどで条件分岐が必要です。 また、自作コンポーサブルはどちらからもアクセスできました。
provideはuseNuxtAppの中に入ってます。
propsは無理そう。 ここでpropsにアクセスできないのきつくないですか? defineNuxtComponentを使うなってことなのかしら。
dataからアクセス可能な項目
ほぼthisへアクセスできるようになっていますが、一部おかしいです。
data…へはアクセスする必要がないものとして、methodsへはアクセスできました。 しかしcomputedが名前はあるのに値がundefinedになります。
コンポーサブルは問題なさそう。 自作の方も問題なし。
provideはthisからもuseNuxtAppもアクセス可能。
propsはこの段階で初めてアクセスできます。
mountedからアクセス可能な項目
インスタンスが完成しているはずなので、thisへもコンポーサブルへもpropsへもほぼフルアクセス可能です。
provideだけuseNuxtApp経由じゃないとアクセスできませんでした。
以上をざっくりと票にまとめるとこんな↓感じになりました。
async | head | data | mount | |
---|---|---|---|---|
data | × | △ | - | ○ |
computed | × | △ | × | ○ |
methods | × | △ | ○ | ○ |
composables | ○ | △ | ○ | ○ |
同自作 | ○ | ○ | ○ | ○ |
provide | ○ | ○ | ○ | △ |
props | × | × | ○ | ○ |
で、実際に触ってみるとheadの実行順が早いのがとにかく迷惑で、どうにかheadでうまく書けないかなーといろいろ試してたんですが、うまい方法がありました。 コレ↓です。
export default defineNuxtComponent({
mounted() {
useHead({
title: 'タイトルだよー',
})
},
})
useHeadはコンポーサブルなだけで、Composition APIとは関係ないのでscript setupじゃなくても書けるんですよ。 で、これをmountedから実行するとmountedは一番最後なので全てにアクセス可能なんです。
一週間くらい悩んでたのがコレで解決しました。
最後にメモ
いやー、defineNuxtComponentがなかったらv3を諦めてたかもしれないくらいに、今の僕には神機能でした。
asyncDataをゴリゴリ使ってるプロジェクトを移植したらasyncDataについては追記しておきます。 まだシンプルなプロジェクトしか移植できてないので、他の要素も追記がだいぶ増えそうな予感。
以上、WordPressからお届けしました!
Nuxt.js v3の目次
- 第1回 インストール方法とかフォルダ構成の違いとか
- 第2回 nuxt.config.jsの書き方の違い
- 第3回 注意が必要そうなモジュール
- 第4回 コンポーネントのscriptの書き方がめっちゃ変わった
- 第5回 trailingSlashに手動で対応
- 第6回 injectの代用品を3つ紹介
- 第7回 v-forとv-ifの併用の代替案
- 第8回 defineNuxtComponentは救世主!(このページ)
- 第9回 composablesがめっちゃ強いかもしれない
- 第10回 手動でAMPに対応する
- 第11回 フック一覧を簡単に調べた
- 第12回 axiosの代用品(useFetch、$fetch、useAsyncData)