これはLaravel Advent Calendar 2022の18日目の記事です。
Laravel Jetstreamのパッケージを利用すると、LivewireやInertiaのパッケージを一緒に導入することが出来ます。
今回はInertia+Vueの使い方を見ていきます。
inertia
inertia.js自体はLaravel独自のJavaScriptアダプターというわけではなく、サーバーサイドはLaravel,Rails、フロントはVue,React、Sveltなどの選択肢が存在しています。MVCフレームワークでなるべく簡単にモダンJSを導入出来ることに価値があるようです。
Jetstreamでの導入
使い方
Laravelに導入した場合、bladeを呼ぶようにルートのURLとパラメータをInertia::render
に渡します
use Illuminate\Http\Request;
use Inertia\Inertia;
/**
*
* @param \Illuminate\Http\Request $request
* @return \Inertia\Response
*/
public function show(Request $request)
{
return Inertia::render('index', [
'message' => 'test',
]);
}
Vueファイル側ではpropsでパラメータを受けることが出来ます。
<script setup >
const props = defineProps<{
message: string;
}>();
</script>
<template>
{{ props.message }}
</template>
この仕組みを使って、認証やルーティングをサーバーサイドのままに、フロントはVueやReactファイルを使って実装出来ます。
props化の仕組みは?
さてパラメータのprops化はどこでやってるのでしょうか...?
inertia.jsとLaravelの間に入るのはinertia-laravel
というアダプター用のコードが用意されています。
何か結構大変なことやってそう...?
Laravel側で呼んでるInertia::render
はFacade定義されたInertia
を使っています。
<?php
namespace Inertia;
use Illuminate\Support\Facades\Facade;
/**
* @method static void setRootView(string $name)
* @method static void share($key, $value = null)
* @method static array getShared(string $key = null, $default = null)
* @method static array flushShared()
* @method static void version($version)
* @method static int|string getVersion()
* @method static LazyProp lazy(callable $callback)
* @method static Response render($component, array $props = [])
* @method static \Illuminate\Http\Response location(string $url)
*
* @see \Inertia\ResponseFactory
*/
class Inertia extends Facade
{
protected static function getFacadeAccessor(): string
{
return ResponseFactory::class;
}
}
実態はResponseFactoryクラス
のようです。
ResponseFactoryのrender
は
/**
* @param string $component
* @param array|Arrayable $props
* @return Response
*/
public function render(string $component, $props = []): Response
{
if ($props instanceof Arrayable) {
$props = $props->toArray();
}
return new Response(
$component,
array_merge($this->sharedProps, $props),
$this->rootView,
$this->getVersion()
);
}
引数で受ける$component
がURLの文字列、$props
がパラメータ配列ですね。
それを利用してResponseクラスのインスタンスを生成しています。
ここのResponseクラスはinertia-laravel
の中にあるクラスです。
このクラスはIlluminate\Contracts\Support\Responsable
というinterface
を継承することでtoResponse
メソッドを持つHTTPレスポンスクラスとして使えます。
/**
* @param string $component
* @param array|Arrayable $props
* @param string $rootView
* @param string $version
*/
public function __construct(string $component, $props, string $rootView = 'app', string $version = '')
{
$this->component = $component;
$this->props = $props instanceof Arrayable ? $props->toArray() : $props;
$this->rootView = $rootView;
$this->version = $version;
}
コンストラクタでclass変数
に引数を保存して、元のController
のreturn
に返ります。
そこからLaravelのコード内でvendor/laravel/framework/src/Illuminate/Routing/Router.php
のtoResponse
メソッドでレスポンスクラスのtoResponse
が呼ばれます
/**
* Static version of prepareResponse.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @param mixed $response
* @return \Symfony\Component\HttpFoundation\Response
*/
public static function toResponse($request, $response)
{
if ($response instanceof Responsable) {
$response = $response->toResponse($request);
}
if ($response instanceof PsrResponseInterface) {
$response = (new HttpFoundationFactory)->createResponse($response);
} elseif ($response instanceof Model && $response->wasRecentlyCreated) {
$response = new JsonResponse($response, 201);
} elseif ($response instanceof Stringable) {
$response = new Response($response->__toString(), 200, ['Content-Type' => 'text/html']);
} elseif (! $response instanceof SymfonyResponse &&
($response instanceof Arrayable ||
$response instanceof Jsonable ||
$response instanceof ArrayObject ||
$response instanceof JsonSerializable ||
$response instanceof stdClass ||
is_array($response))) {
$response = new JsonResponse($response);
} elseif (! $response instanceof SymfonyResponse) {
$response = new Response($response, 200, ['Content-Type' => 'text/html']);
}
if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
$response->setNotModified();
}
return $response->prepare($request);
}
Responsable
のinstanceであればtoResponse
メソッドが呼ばれています。
inertia-laravel
の中にあるResponse
クラスです。
...
use Illuminate\Support\Facades\Response as ResponseFactory;
...
/**
* Create an HTTP response that represents the object.
*
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
public function toResponse($request)
{
$only = array_filter(explode(',', $request->header('X-Inertia-Partial-Data', '')));
$props = ($only && $request->header('X-Inertia-Partial-Component') === $this->component)
? Arr::only($this->props, $only)
: array_filter($this->props, static function ($prop) {
return ! ($prop instanceof LazyProp);
});
$props = $this->resolvePropertyInstances($props, $request);
$page = [
'component' => $this->component,
'props' => $props,
'url' => $request->getBaseUrl().$request->getRequestUri(),
'version' => $this->version,
];
if ($request->header('X-Inertia')) {
return new JsonResponse($page, 200, ['X-Inertia' => 'true']);
}
return ResponseFactory::view($this->rootView, $this->viewData + ['page' => $page]);
}
最終的には$page
に情報を詰めて既にX-Inertia
がtrueの場合JsonResponse
を返す。
そうでない場合Facade
のResponse
でviewメソッド
を呼びます。
X-Inertia
にまつわる挙動に関してはこちらの記事がとても詳しく参考になります。
Facade
のResponse
のview
に行った後、makeメソッドでviewを通したresponseを作っています。
/**
* Create a new response for a given view.
*
* @param string|array $view
* @param array $data
* @param int $status
* @param array $headers
* @return \Illuminate\Http\Response
*/
public function view($view, $data = [], $status = 200, array $headers = [])
{
if (is_array($view)) {
return $this->make($this->view->first($view, $data), $status, $headers);
}
return $this->make($this->view->make($view, $data), $status, $headers);
}
という感じでこの先は通常のview
の処理になっていきます。
渡したパラメータはアダプター内の変換でprops
をkey名
にしてテンプレート側へ渡っていきました。
さらにfront側でどう受け取って扱っていくかは今度はjs側のアダプターコードを読んでいくことで分かりそうです。
これはまた別の機会に読んでみようと思います。