Laravel の公式サポート下となった Inertia
。
皆さん遊んでいますか!!
結論だけ知りたければ一番下へ。
はじめに
SPA のサイトを組んでいると、ドロップダウンの補完検索だったり、ダイアログ内で動的にデータを取ってきたり、意外と Ajax 通信を行いたい場面が出てきます。
(そういう時に使える Partial reloads は別のお話。部分更新なので、少しだけ用途が異なる。)
例えばユーザー検索機能。
順当にいくと、下記の二種類のルートをメンテナンスしなければなりません。
- Inertia画面表示用のクエリ構築
- API用の検索クエリ構築
DDD
なりでしっかり組んでおけば Repository 層
にでも逃がせますが、Inertia の特徴の爆速開発とは相性が悪いです。
これをどうにかまとめられないか...。
今回は Welcome ページに以下のデータを付与したサンプルを用意する。
Route::get('/', function () {
return Inertia::render('Welcome', [
'message' => 'データを表示',
'record' => [
['id' => 10, 'name' => '製品A'],
['id' => 11, 'name' => '製品B']
]
]);
})->name('home');
Inertia の仕組み
Inertia
のプロトコルは意外と単純。
1回目のページアクセスでは、SPAのサイトHTMLと画面に渡すデータを含めた情報がHTMLで返却されます。
つまりフルリロードがかかります。
ちなみに初回渡したデータは body > div#app
タグ内の data-page
にユニコードをエンコードした JSON 形式でそのまま入っています。
これが props としてページコンポーネントに渡る感じですね。
丸見えが気になる方はこちらの記事を参照してください!
この状態ので次の「Log in」画面へのボタンをクリックすると、先ほどの JSON データのみ取得されます。
先ほどの Welcome 画面を同じやり方で無理やり閲覧すると、JSON の中にちゃんとデータが入っています。
この仕組みで、Laravel と連携した SPA が構築されています。
あれ、まとまってない?
よく考えると、Inertia って同じURLへのアクセスの仕組みなのに、フルリロードとAPI取得、使い分けてるな?
これはリクエストを送る際のヘッダーを Inertia が確認して、処理を変えているからです。
X-Inertia: true
フルリロードか JSON 取得を行うかは、このパラメタがキーになっているかで変わる。
フルリロードの際は、このヘッダは未指定です。
JSON取得の際は、X-Inertia: true
が付与されます。
X-Inertia-Version: xxxxxxxx
実は X-Inertia: true
だけではダメで、この X-Inertia-Version
も必要。
これは初期設定ではリソースアセットのハッシュ値が格納されていて、以前と同じリソースを取得しているかを判断している。
バージョン値が異なる場合、 409 Conflict
が返ってくるのでセッション中にビルドが走っても安全!
より安全な SPA サイトが構築できる。
ちなみにこれは app/Http/Middleware/HandleInertiaRequests.php
の versions()
で定義されていて、データの下のほうに表示されている。
この場合は 865d0c0e6919101770e1358e0634d03d
じゃぁやってみよう (Axios)
では実践あるのみ。
先ほどの Welcome ページを改造してみる。
<script setup lang="ts">
import { Head, Link, usePage } from '@inertiajs/vue3'
import axios from 'axios'
import { onMounted } from 'vue'
const { version } = usePage()
onMounted(async () => {
const { data } = await axios.get(route('home'), {
headers: {
'X-Inertia': true,
'X-Inertia-Version': version
}
})
console.log(data)
})
</script>
ちゃんと値が取れた。
props
には、Inertia の値も含まれているので注意。
実践編
実際はこれを Composable
なのでラップし、型も当てて使いやすくするとよい。
import { usePage } from '@inertiajs/vue3'
import axios, { AxiosRequestConfig } from 'axios'
type Method = 'get' | 'post' | 'put' | 'patch' | 'delete'
export const useAxios = () => {
const ajax = async <T, D = any>(method: Method, url: string, data?: D, config?: AxiosRequestConfig<any>) => {
const { version } = usePage()
const fixedConfig: AxiosRequestConfig<any> = {
...config,
headers: {
...config?.headers,
'X-Inertia': true,
'X-Inertia-Version': version,
}
}
const res = method === 'get'
? await axios[method](url, fixedConfig)
: await axios[method](url, data, fixedConfig)
const result: T = (res.data as any)?.props
return result
}
const get = async <T>(url: string, config?: AxiosRequestConfig<any>) => {
return ajax<T>('get', url, undefined, config)
}
const post = async <T, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>) => {
return ajax<T, D>('post', url, data, config)
}
const put = async <T, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>) => {
return ajax<T, D>('post', url, data, config)
}
const patch = async <T, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>) => {
return ajax<T, D>('post', url, data, config)
}
const del = async <T, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>) => {
return ajax<T, D>('delete', url, data, config)
}
return { get, post, put, patch, del }
}
これを axios の代わりに使う。
すごく簡易実装になっているので、必要なものがあれば増やしてください。
<script setup lang="ts">
import { useAxios } from '@/composables/useAxios'
import { Head, Link, usePage } from '@inertiajs/vue3'
import { onMounted } from 'vue'
const { get } = useAxios()
onMounted(async () => {
const data = await get<{
message: string
record: { id: number, name: string }
}>(route('home'))
console.log(data)
})
</script>
おわりに
Ajax ついでに Inertia の仕組みも学べてお得!
マスタ取得系は、このように無理やり取得することが多いですね。
例えばユーザー配列は Props に持たせて Partial Reload するのが奇麗ではあるのですが、
コンポーネントの中にロジックまで閉じ込めたいときは便利です。
みんなも爆速 Inertia 開発!