■ Partial reloads
Partial Reloadsは、Inertia.jsの強力な機能の一つで、ページの特定の部分だけを更新することができます。これにより、全ページを再読み込みする必要がなくなり、ユーザーエクスペリエンスが向上します。たとえば、ユーザーがフォームを送信した後にリストのみを更新したい場合や、特定のコンポーネントのデータを更新したい場合に便利です。
本記事では、Laravel×ReactのコードベースでPartial reloadsの動きを確認します。
■ Partial reloadsでページの一部だけを更新する
ユーザー検索機能を例にPartial reloadsでページの一部だけを更新する場合の実装を確認していきます。Inertia自体にそこまで馴染みがない方もいると思うので、順を追って丁寧に機能を実装していきます。
▼ 準備
まずはユーザー検索機能を付けずに、Inertiaを利用したシンプルなユーザー一覧ページを用意します。
type Props = {
users: {
id: number;
name: string;
}[];
};
export default function List(props: Props) {
const { users } = props;
return (
<div>
<h1>ユーザー一覧</h1>
<ul>
{users.map((user) => <li key={user.id}>{user.name}</li>)}
</ul>
</div>
);
}
<?php
namespace App\Http\Controllers\User\List;
use Inertia\Inertia;
use App\Models\User;
class Controller {
public function __invoke() {
$users = User::all();
return Inertia::render("User/List", [
"users" => $users,
]);
}
}
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\User;
Route::get('/user', User\List\Controller::class);
▼ ユーザー検索機能追加
この一覧ページにユーザーの名前検索機能を追加していきます。検索時にPartial reloadsすることで、ページを再読み込みすることなく、ユーザー名部分だけ更新することができます。
まずは、ユーザー一覧のリクエストクラスを追加し、コントローラー部分を修正して、検索処理を追加します。
<?php
namespace App\Http\Controllers\User\List;
use Illuminate\Foundation\Http\FormRequest;
class Request extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'sometimes|string|max:255',
];
}
}
<?php
namespace App\Http\Controllers\User\List;
use Inertia\Inertia;
use App\Models\User;
class Controller {
- public function __invoke() {
+ public function __invoke(Request $request) {
+ $search_name = $request->query("name");
- $users = User::all();
+ $users = User::query()
+ ->when($search_name, fn ($query, $search_name) => $query->where("name", "like", "%{$search_name}%"))
+ ->get();
return Inertia::render("User/List", [
"users" => $users,
]);
}
}
次に、フロントエンド側の準備です。検索フォームコンポーネントを用意します。
import { useState } from 'react';
type Props = {
handleSearch: (name: string) => void;
}
export default function SearchForm(props: Props) {
const { handleSearch } = props;
const [name, setName] = useState('');
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
};
return (
<div>
<input type="text" value={name} onChange={handleNameChange} />
<button onClick={() => handleSearch(name)}>検索</button>
</div>
);
}
これを使用して、検索機能を完成させます。
+ import SearchForm from "@/Components/User/List/SearchForm";
+ import { router } from "@inertiajs/react";
type Props = {
users: {
id: number;
name: string;
}[];
};
export default function List(props: Props) {
const { users } = props;
+ const handleSearch = (name: string) => {
+ router.reload({
+ data: { name }, // クエリパラメータにnameを追加
+ only: ['users'], // usersのみ再取得 👈 Partial reloads!
+ })
+ };
return (
<div>
<h1>ユーザー一覧</h1>
+ <SearchForm handleSearch={handleSearch} />
<ul>
{users.map((user) => <li key={user.id}>{user.name}</li>)}
</ul>
</div>
);
}
■ Lazy data evaluation
Lazy data evaluationについて解説します。説明のために、次のような2つのサイコロの目を振るページを用意しました。
import { router } from "@inertiajs/react";
type Props = {
dice1: number;
dice2: number;
};
export default function Dice(props: Props) {
const { dice1, dice2 } = props;
const rollDice1 = () => {
router.reload({
only: ['dice1'],
});
};
const rollDice2 = () => {
router.reload({
only: ['dice2'],
});
}
return (
<>
<p>サイコロ1: {dice1}</p>
<button onClick={rollDice1}>サイコロ1を振り直す</button>
<p>サイコロ2: {dice2}</p>
<button onClick={rollDice2}>サイコロ2を振り直す</button>
</>
);
}
<?php
namespace App\Http\Controllers\Dice;
use Illuminate\Support\Facades\Log;
use Inertia\Inertia;
class Controller {
public function __invoke() {
$roll1 = function () {
Log::info("サイコロ1が振られました");
return random_int(1,6);
};
$roll2 = function () {
Log::info("サイコロ2が振られました");
return random_int(1,6);
};
return Inertia::render("Dice", [
"dice1" => $roll1(),
"dice2" => $roll2(),
]);
}
}
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Dice;
Route::get("/dice", Dice\Controller::class);
さて、どちらもPartial reloadsでデータを取得しているので、サイコロ1を振り直せばサイコロ1の目だけが変わります。サイコロ2についても同様です。しかし、ログを確認すると、どちらのサイコロも振られています。
つまり、現状だと、$roll1
、$roll2
がともに実行されてから、どのデータだけを返却するのか評価していることになります。
// ...
class Controller {
public function __invoke() {
// ...
return Inertia::render("Dice", [
"dice1" => $roll1(), // 👈 $roll1が実行されてからdice1を返すかどうか評価している
"dice2" => $roll2(), // 👈 $roll2が実行されてからdice2を返すかどうか評価している
]);
}
}
サイコロ1のデータしか必要ないときに、サイコロ2も振ってしまうのは無駄ですから、振らないようにしたいです。そこで、どちらサイコロのデータが必要なのか先に判断し、そのサイコロだけのデータを取得するようにします。このように、データが必要になったときだけ、データ取得処理が実行されるようにする方法がLazy data evaluationです。これができれば無駄な処理が実行されずに済みます。
実装方法は簡単で、データ取得処理をクロージャーで包むだけです。
// ...
class Controller {
public function __invoke() {
// ...
return Inertia::render("Dice", [
"dice1" => fn () => $roll1(), // 👈 $dice1を返す場合のみ$roll1が実行される
"dice2" => fn () => $roll2(), // 👈 $dice2を返す場合のみ$roll2が実行される
]);
}
}
こうすることで、必要なデータ取得処理だけが実行できるようになります。
さらに、初期表示時には、データを取得したくないというケースもあるでしょう。そういう場合はさらに、Inertia::lazy()
でデータ取得部分をラッピングします。
// ...
class Controller {
public function __invoke() {
// ...
return Inertia::render("Dice", [
"dice1" => Inertia::lazy(fn () => $roll1()),
"dice2" => Inertia::lazy(fn () => $roll2()),
]);
}
}