LaravelではじめるReact.jsのSPAアプリ開発 (2)
LaravelとReact.jsでSPAアプリを開発する手法について解説する記事の2回目です。表紙はこちらです。
◆
前回までにプロジェクトをセットアップし、ビルドして動作確認出来る所まで進めました。本項ではTodoアプリの作成を通してReactでの開発を解説していきます。
何故Todoアプリなのかというと、繰り返しや完了などのステータス変更などのSPAで関連する基礎的な内容が網羅されているからです。
# Todoアプリの設計
Todoアプリにはどのようなコンポーネントが必要なのか、まずは考えたいと思います。
アプリとして要件が定められているわけではありませんが、最低限Todoアプリとしての体裁を整えるには下記の2種類のUIが必要です。
・タスクを追加するフォーム
・タスク一覧
上記のUIに合わせて、アプリ全体を下記のようなデザインで作成しました。
Reactで何かを作る時は事前にHTMLでUIを作成する方がよいです。どんなコンポーネントが必要になるか、機能がどの程度発生するかを事前に検討する必要があります。
Reactではこれらの役割に応じてコンポーネントに展開していきます。一覧の場合は「一覧を管理するもの」と「1個の要素」の2種類のコンポーネントを用意するとよいでしょう。
今回は次の4個のコンポーネントを作成していきます。
アプリ全体のコンポーネント → Appコンポーネント
タスクを追加するフォーム → TodoFormコンポーネント
タスク一覧 → TodoListコンポーネント
タスク単体 → TodoListItemコンポーネント
どこまでをコンポーネント化するべきか、という疑問には多くの議論が発生する場所で、例えばボタンやラベルといった要素すべてをコンポーネント化するべきと主張する人もいます。
本項では入門とするため、コンポーネント化は最低限としています。
# Appコンポーネントの作成
Example.jsと同階層に「resources/js/components/App.js」を作成していきます。
App.js
import React from 'react';
function App() {
return (
<div className="container">
<div className="row justify-content-center">
<div className="col-md-8">
<div className="card">
<div className="card-header">ToDo App</div>
<div className="card-body">
{ /* form */ }
<form>
<h2>タスクの追加</h2>
<div className="form-group">
<input
type="text"
className="form-control"
name="text"
autoComplete="off" />
</div>
<button
type="submit"
className="btn btn-primary"
>追加</button>
</form>
{ /* list */ }
<div className="todo-list mt-3">
<h2>タスク一覧</h2>
<ul>
<li><input type="checkbox" /> あいうえお</li>
<li><input type="checkbox" checked /> かきくけこ</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default App;
AppコンポーネントはExample.jsの構造をそのまま参考にしました。まだ個々の要素をコンポーネント化せずにHTMLをそのまま書いています。
コンポーネント化する部分をコメントでメモしてあります。JSXでのコメントはHTML部分では「{ /* コメント */ }」の形式で入力します。
ここでビルドを行い、ブラウザで動作を確認するのですが今後コードの修正のたびにビルドを行うのは非常に不便です。
laravel/uiでセットアップされた環境ではファイルの更新をチェックして自動でビルドを実行する機能が用意されています。
npm run devの代わりにターミナルで次のコマンドを実行します。
npm run watch
このターミナルは待機状態になるため操作できなくなりますが、監視が走ってファイルが自動更新されるようになります。自動ビルドが発生するタイミングはファイルを更新したタイミングなので、先程作成したApp.jsを再保存して(改行を足すなどして内容を変更して再保存)ビルドを実行してみましょう。
DONE Compiled successfully in 254ms 11:53:41
Asset Size Chunks Chunk Names
/css/app.css 179 KiB /js/app [emitted] /js/app
/js/app.js 2.01 MiB /js/app [emitted] /js/app
このように出力されればOKです。1ファイルの修正だけなので254ms = 0.2秒で完了しています。npm run devですべてビルドするより高速にビルドが行います。ただ1ファイル保存するたびにビルドが発生するのと監視のためのプログラムが動作するので環境によっては重たく感じるかもしれません。
Laravel+Reactで開発中は開発サーバー用、Reactのビルド用、arisanコマンド実行用で合計3個のターミナルを使うことになります。
# Appコンポーネントの表示
ビルドするだけでは表示されません。Exampleコンポーネントを表示するコードがExample.jsにあったように描画コードとLaravel側にも作業が必要です。
まずはapp.blade.phpを編集してExampleコンポーネントを描画していた部分を下記のように書き換えます。
<div class="container py-5">
<div id="exmple"></div>
</div>
↓
<div class="container py-5">
<div id="app"></div>
</div>
<div id="app"></div>に描画する形で指定していますが、IDはappではなくtodo-appなどの指定でも問題ありません。Exampleコンポーネントの描画は消えてしまいます。もし残したい場合は<div id="example"></div>はそのままにして追加する形で修正してください。
続いてresources/js/app.jsを編集しAppコンポーネントの描画命令を追加します。
resources/js/app.js
require('./components/Example');
import React from "react";
import ReactDOM from 'react-dom';
import App from "./components/App";
ReactDOM.render(<App />, document.getElementById('app'));
<div id="app"></div>にAppコンポーネントを描画する指定を追加しました。
app.jsを保存すると自動でビルドが発生します。ビルドが完了したらブラウザで動作確認しましょう。このように表示されればOKです。
表示されない場合はビルドに失敗している可能性があります。監視が動いているターミナルを確認してください。
不具合のあるコードと行数がエラーに表示されています。
# TodoListコンポーネント、TodoFormコンポーネントの作成
Appに追加する2種類のコンポーネントを作成していきます。それぞれのコンポーネントはApp.jsで書いたHTMLをそのまま入力していきます。
resouces/js/components/TodoList.js
import React from 'react';
function TodoList() {
return (
<div className="todo-list mt-3">
<h2>タスク一覧</h2>
<ul>
<li><input type="checkbox" /> あいうえお</li>
<li><input type="checkbox" checked /> かきくけこ</li>
</ul>
</div>
);
}
export default TodoList;
resources/js/TodoForm.js
import React from 'react';
function TodoForm() {
return (
<form>
<h2>タスクの追加</h2>
<div className="form-group">
<input
type="text"
className="form-control"
name="text"
autoComplete="off"
/>
</div>
<button
type="submit"
className="btn btn-primary"
>追加</button>
</form>
);
}
export default TodoForm;
Appコンポーネントを変更し、作成した2個のコンポーネントを組み込んでいきます。
import React from 'react';
import TodoForm from "./TodoForm";
import TodoList from "./TodoList";
function App() {
return (
<div className="container">
<div className="row justify-content-center">
<div className="col-md-8">
<div className="card">
<div className="card-header">ToDo App</div>
<div className="card-body">
<TodoForm />
<TodoList />
</div>
</div>
</div>
</div>
</div>
);
}
export default App;
import文を先頭に追加し、作成した2個のコンポーネントを読み込みんでいます。HTML部分をそのままコンポーネントに置き換える形です。コードがすっきりしてきましたね。
ブラウザで表示を確認してみましょう。HTMLをそのままコピーしただけなので見た目は全く変わらないはずです。
# Reactでの繰り返し処理
TodoListコンポーネントはHTMLをベタ書きの状態ですが、この部分はタスクを繰り返しで表示する必要があります。
例えばtodo_listに次のような文字列の配列があった場合Reactではどのように書くでしょうか。
const todo_list = ["あいうえお", "かきくけこ"];
例えばLaravelのbladeでは@foreach構文を使って次のように書きます。
<ul>
@foreach ($todo_list as $todo)
<li><input type="checkbox" />{{ $todo }}</li>
@endforeach
</ul>
Reactでは配列を変換するmap関数を利用して次のように書きます。
TodoList.js
function TodoList() {
const todo_list = ["あいうえお", "かきくけこ", "さしすせそ"];
return (
<div className="todo-list mt-3">
<h2>タスク一覧</h2>
<ul>
{
todo_list.map((todo, index) => {
return <li className="todo-item" key={index}>
<input type="checkbox" />
<span>{ todo }</span>
</li>
})
}
</ul>
</div>
);
}
読みづらいかもしれませんが、map関数を使って「todo_list」というデータを「<li>〜</li>のJSXに変換する」というコードです。
Reactには標準ではループをきれいに書くための機能が無いため、このような書き方になっています。他にも手法がいくつもあり、例えばプラグインを入れることで@foreachのような記述にすることも出来ます。
ブラウザで繰り返し処理が正しく動いているか確認してみましょう。
「さしすせそ」が追加されていますね。
# データの受け渡し
タスク一覧のデータをTodoList.jsに書いていますが、実際のアプリでは使うことが出来ません。Controllerでデータを読み込んでViewに渡すようにデータは渡されて表示する形にする必要があります。
AppコンポーネントからTodoListコンポーネントへと、データを受け渡しが出来るようにする必要があります。
まずはデータを渡す側のApp.jsを変更します。親から子にデータを渡すにはHTML部分の属性を使うことで簡単に渡すことができます。
App.js
function App() {
const todo_list = [
"Appから",
"TodoListにデータを",
"正しく渡せているか確認"
];
return (
<div className="container">
<div className="row justify-content-center">
<div className="col-md-8">
<div className="card">
<div className="card-header">ToDo App</div>
<div className="card-body">
<TodoForm />
<TodoList items={ todo_list } />
</div>
</div>
</div>
</div>
</div>
);
}
TodoListに「items」という属性で作成したタスク一覧のtodo_listを渡しています。
続いて、受け取り側を変更します。親から渡されたデータは「props」と呼ばれます。props = プロパティ = 属性です。親から子に様々な属性を渡すことで制御することができます。
propsはコンポーネントの関数の引数に「props」を追加することで取得することができます。
TodoList.js
function TodoList(props) {
const todo_list = props.items;
return (
<div className="todo-list mt-3">
<h2>タスク一覧</h2>
<ul>
{
todo_list.map((todo, index) => {
return (<li className="todo-item" key={index}>
<input type="checkbox" />
<span>{ todo }</span>
</li>)
})
}
</ul>
</div>
);
}
TodoListの引数にpropsを追加することで親から渡されたデータを簡単に受け取ることが出来ます。
属性のキーがそのままpropsのプロパティになっています。属性なので複数個指定することも出来ます。
親から子 属性で渡す
<Child key1={ value } key2={ value2 } />
子 propsで受け取り
const value1 = props.key1
const value2 = props.key2
ブラウザで動作確認し、表示を確認しましょう。
Appから渡した配列がTodoListで描画されていますね。
# まとめ
Todoアプリの開発の2回目としてコンポーネントのセットアップを進めました。本項で解説した内容についてまとめます。
・新しく追加したコンポーネントの描画の流れ
・コンポーネントからコンポーネントを読み込む方法
・親コンポーネントから子コンポーネントへデータを渡す方法
・ループの作成方法
次回はフォームを使ってデータを追加し、TodoListを更新する流れについて解説していきます。
完成まで突っ走る意気込みです。サポートしていただけると非常に嬉しいです。応援よろしくお願いします。