メモ > 技術 > フレームワーク: Laravel10 > Reactを使う(登録の実例)
Reactを使う(登録の実例)
以下で紹介されている「簡易 Todo アプリ」を参考に作成する。
Inertia.js を用いた Laravel React SPA 開発のメリットデメリット。 - ガオラボ
https://tech.gaogao.asia/react-inertia-laravel/
■テーブルの作成
$ sail artisan make:migration create_tasks_table
作成されたファイルを、以下のとおり修正する。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->string('name')->comment('タスク名');
$table->timestamps();
$table->comment('タスク');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('tasks');
}
};
マイグレーションを実行。
$ sail artisan migrate
■モデルの作成
$ sail artisan make:model Task
作成されたファイルを、以下のとおり修正する。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
protected $fillable = [
'name',
];
}
■コントローラーの作成
$ sail artisan make:controller TaskController
作成されたファイルを、以下のとおり修正する。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use App\Models\Task;
use Illuminate\Support\Facades\Redirect;
use Inertia\Inertia;
use Inertia\Response as InertiaResponse;
class TaskController extends Controller
{
public function index(): InertiaResponse
{
$tasks = Task::all();
return Inertia::render('Task/Index', [
'tasks' => $tasks,
]);
}
public function store(Request $request): RedirectResponse
{
Task::create($request->validate([
'name' => ['required', 'max:20'],
]));
return Redirect::route('task.index');
}
}
■ビューの作成
以下のファイルを作成する。
resources/js/Pages/Task/Index.jsx を作成。
import GuestLayout from '@/Layouts/GuestLayout';
import { Head, useForm } from "@inertiajs/react";
export default function TaskPage({ tasks }) {
const { data, setData, post, errors } = useForm({
name: ''
});
const submit = e => {
e.preventDefault();
post('/task', {
onSuccess: () => setData('name', '')
});
};
return (
<GuestLayout>
<Head title="Task" />
<div>
<h2 className="font-semibold text-xl text-gray-800 leading-tight pb-4">Tasks</h2>
<form onSubmit={submit} className="mb-4">
<div>
<label className="me-2">タスク</label>
<input
value={data.name}
onChange={ e => setData('name', e.target.value) }
className="border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm mt-1 me-2"
/>
<button type="submit" className="items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150">
登録
</button>
{errors.name && <div className="mt-2 text-red-600">{errors.name}</div>}
</div>
</form>
<ul>
{tasks.map((task, index) => (
<li key={index} className="mb-2">
{task.id}: {task.name}
</li>
))}
</ul>
</div>
</GuestLayout>
);
}
■ルーティングの設定
以下のとおり設定する。
routes/web.php
use App\Http\Controllers\TaskController;
Route::get('/task', [TaskController::class, 'index'])->name('task.index');
Route::post('/task', [TaskController::class, 'store']);
■動作確認
http://laravel.local/task
■補足1(TypeScriptを使用している場合)
TypeScriptを使用している場合、型の指定を追加する必要がある。
一例だが、以下のようにして対応できる。
resources/js/Models/Task.ts を作成。
export default interface Task {
id: number;
name: string;
}
resources/js/Pages/Task/Index.jsx を編集。
import GuestLayout from '@/Layouts/GuestLayout';
import { Head, useForm } from "@inertiajs/react";
import Task from '@/Models/Task'; // 型の指定を読み込む
export default function TaskPage({ tasks }: { tasks: Array<Task> }) { // 型の指定
const { data, setData, post, errors } = useForm({
name: ''
});
const submit = (e: React.FormEvent<HTMLFormElement>) => { // 型の指定
e.preventDefault();
post('/task', {
onSuccess: () => setData('name', '')
});
};
return (
<GuestLayout>
〜略〜
</GuestLayout>
);
}
ファイルの置き場所として resources/js/Models が適切かどうかは検討したい。
あくまで型定義しか書かないのなら、resources/js/Interfaces の方が適切か。
(resources/js/Types もいいかと思ったが、resources/js/types がすでに存在し、Userなどの型定義が置かれている。
ここはフレームワークデフォルトの型置き場として触らない方がいいか。
その場合でも、Userは専用ファイルに移動させるべきか。)
なお、モデルの配列指定は、以下のように書くこともできる。
export default function TaskPage({ tasks }: { tasks: Array<Task> }) {
↓
export default function TaskPage({ tasks }: { tasks: Task[] }) {
なお、エラーを回避するだけの暫定対応なら、以下のとおり「any」を指定することもできる。
export default function TaskPage({ tasks }: { tasks: Array<any> }) { // 型の指定
const { data, setData, post, errors } = useForm({
name: ''
});
const submit = (e: any) => { // 型の指定
■補足2
型定義を
import Task from '@/Models/Task';
として読み込む際、
export default function Task({ tasks }: { tasks: Array<Task> }) {
が存在していると名前の重複によりエラーになる。
この場合、
export default function TaskIndex({ tasks }: { tasks: Array<Task> }) {
のようにページの関数名を変更するか。
もしくは
import { default as TaskModel } from '@/Models/Task';
としてモデルを別名で読み込むことで対処できる。
■補足3
「const { data, setData, post, errors } = useForm(〜略〜);」という書き方はReactならではなものでは無く、JavaScriptの文法に沿ったもの。
オブジェクトの分割代入と呼ばれるもので、以下のように値を受け取ることができる。
function sample() {
return {
name: 'John Doe',
age: 24,
occupation: 'Developer',
isAdult: function() {
if (age >= 20) {
return true;
} else {
return false;
}
}
};
}
// オブジェクトの分割代入を使ってプロパティを取り出す
const { name, age, isAdult } = sample();
console.log(name); // John Doe
console.log(age); // 24
console.log(isAdult()); // true
■補足4
CSRF対策のためのコードを記載していないが、何もしなくても自動的に対策が行われている。
具体的には、トークンが X-CSRF-TOKEN ヘッダに組み込まれ、これにより認証が行われている。