🎀

フロントエンドエンジニアが挑戦してみた!Laravel初心者のためのコンポーネント入門

に公開

はじめに

なんでLaravelでコンポーネントを作ることになったのか

フロントエンドエンジニアの皆様、ごきげんよう。
皆様はバックエンドがLaravel(PHP)で構築された環境にて、お手を煩わせたご経験はございませんこと?
わたくしも、かつてそのような状況に身を置いたことがございますの。

「いつも通りHTML構造で...」と考えておりましたが「せっかくの機会でございますし、Laravelの形式でコンポーネントをお作りしてみようかしら」と思い立ち、挑戦してみましたところ、はじめは少々戸惑いましたけれども、思いもよらぬ気づきがございましたのよ。

(お嬢様言葉慣れないので戻します)

前提

Laravel経験なし。
PHPもそんなに触り慣れていない(かなり久しぶりの状態)。

案の定Laravel公式なんて見れてない。
あまりにも初見すぎたのでClaudeのお嬢様にPHP基Laravelのお作法を教えてもらいながら頑張った。

この記事は...

Laravel・PHPともに初心者の私がLaravelのコンポーネントシステムを理解して実装するまでの道のり、そして実際に作ってみて感じたメリット・注意点を共有します。
Next.jsやVue.jsの経験があれば、意外と取っつきやすいのでLaravelコンポーネント作りやすいかもしれないです!

Laravelコンポーネントの基本

コンポーネントの作り方が2パターンある

Laravelでコンポーネントを作る場合、2パターンの作り方ができます。

Laravelコンポーネントのタイプ

  • Bladeコンポーネント
  • クラスベースのコンポーネント

Bladeコンポーネント

ララベルの中では一般的な作りでHTML構造とPHPのロジックを組み合わせて作るタイプ。
今回私はこちらの作りを試したので本記事はこのコンポーネントの作りにフォーカスしています。

クラスベースのコンポーネント

そのままの意味合いでPHPのクラスファイルとそれに対応するBladeテンプレートファイルで構成されています。
PHPのクラスを使うことができるので、複雑なUIやメソッドを使うのであればクラスベースの方が再利用性に長けていそうな印象。

複雑なケースが想定されるコンポーネントとは

いまいち想像しにくいのですが一例としてこんな時?

①都道府県セレクトボックスで都道府県を選ぶと「市区町村」セレクトボックスの中身が選択したものに合わせて非同期で切り替わる

②とあるチェックボックスがonになったら追加の入力フィールドが表示されるとか
(よくある「はい」を選択したら「その理由はなんですか」の入力フィールドが出てくるアンケートフォームとか)

③数ステップで構成される予約フォームとかで現在のステップ数や進捗状況の表示、ボタンの有効/無効制御などのロジックをクラスで管理、表示はBladeテンプレートで

④日付の指定があり、複数のページにまたがって表示させるなどの共通の処理を持つUI部品

⑤予約状況によってボタンの表示テキストや色を変更する..などの複数の条件を持つとき

単にHTMLを表示するだけでなく何らかの処理や状態管理を伴うUI部品を作成する際にはクラスベースでのコンポーネントが良さげかもしれない。

フロントエンドとしてどっちが良いか

複雑でない限り基本的にBladeコンポーネントの作りの方が良いのでは?と思いました。
そう思った理由の中に「学習コストが低い」「シンプルで分かりやすい」というものがあります。

知人のバックエンドエンジニアに聞くと「自分の好きな方で」という回答が圧倒的に多かったです。(さすがバックエンドエンジニア!)
フロントエンドエンジニア且つ私のようなバックエンド初心者の場合、作りたいものの設計次第ではバックエンドエンジニアにコンポーネント作るならどっちが後々困らないか話すのも大事。

Bladeコンポーネントのディレクトリ構造

コンポーネントファイル

LaravelでBladeコンポーネントを作ろうとなった場合、resources/views/components/の中にコンポーネントファイルをどんどん追加してゆきます。
この中に追加すると<x-コンポーネント名>で呼び出せるようになります。(Bladeディレクティブというらしい)

階層構造のコンポーネント参照方法

<x-コンポーネント名>のこのx-の接頭辞部分がresources/views/components/のディレクトリを見ているらしい。
この書き方はreact/Next/Vue.jsとは違う呼び出しになるので最初すごく戸惑いがありました。

また、コンポーネントのディレクトリのなかに/ui//forms/のように管理方法を分ける場合の呼び出し方は以下のようになります。

  • resources/views/components/button.blade.php<x-button></x-button>
  • resources/views/components/ui/card.blade.php<x-ui.card></x-ui.card>
  • resources/views/components/forms/input.blade.php<x-forms.input></x-forms.input>

ディレクトリの区切りは .で区切るので、この書き方さえ覚えておけばコンポーネントを階層化してもスムーズに呼び出せるのが良いなと思いました。

assetsファイル

画像やフォント、動画などのファイルはpublic/配下の中に入れます。
画像などのアセット参照の仕方は{{ asset('images/example.jpg') }}のような感じになります。

⚠️注意「クラスベースのコンポーネントのファイル位置」

クラスベースのコンポーネントを活用する場合は
Bladeテンプレートファイルは上記と変わらず、resources/views/components/ここにコンポーネント名.blade.phpで準備しておき、
phpクラスファイルをapp/View/Components/ここにコンポーネント名.phpのように用意します。

コンポーネントファイルの作り

コンポーネントファイルの構造は主に2つの部分で成り立っている感じです。

  1. props(プロパティ)の定義
    @props(['id', 'name', ...]) のように、コンポーネントが受け取る値を最初に宣言します。
    これによって状態変化に対応できます。

  2. コンポーネントのHTMLマークアップ:
    その下にコンポーネントの実際の見た目や機能を記述します。

この構造はNext.jsのコンポーネント設計に似ているので、直感的に理解しやすいはず....です。
私も普段フロントエンド開発をしているので、この部分は特に馴染みやすく感じました!

コンポーネントのサンプル1

サンプルでラジオボタンのコンポーネントを用意してみます。

▼コンポーネントファイル

@props(['id', 'name', 'value', 'checked' => false, 'label' => ''])

<div class="radio">
    <input type="radio" 
           id="{{ $id }}" 
           name="{{ $name }}" 
           value="{{ $value }}" 
           {{ $checked ? 'checked' : '' }}
           {{ $attributes->merge(['class' => 'form-radio']) }}
    >
    <label for="{{ $id }}">{{ $label }}</label>
</div>

▼使用例

<x-radio-button id="color-red" name="color" value="red" label="赤" checked />
<x-radio-button id="color-blue" name="color" value="blue" label="青" />
<x-radio-button id="color-green" name="color" value="green" label="緑" />

コンポーネントのサンプル2

続いてよく使うのはボタンだと思うのでボタンコンポーネントを作ってみます。

▼コンポーネントファイル

@props([
    'type' => 'main',
    'size' => 'M',
    'disabled' => false,
])

<button
    {{ $attributes->merge([
        'class' => 'c-btn c-btn__' . $type . ($size !== 'M' ? ' c-btn__' . $size : ''),
        'disabled' => $disabled ? 'disabled' : false,
    ]) }}>
    {{ $slot }}
</button>

▼使用例

<div>
    <x-button>メインボタン</x-button>
    <x-button size="S" disabled="true">サイズS非活性化</x-button>
    <x-button type="sub" size="L" disabled="true">サイズL非活性化</x-button>
</div>

popsが便利

ボタンサイズを3種類(S/M/L)などだし分ける場合もpropsで指定しました。
propsに書いたものがデフォルト(コンポーネントのベース設定)になるのでメソッド内に何も書かないとpropsの状態を発揮します。
レイアウトもクラスわけさえしておけばここの出しわけで見た目を変えれるので楽でした。
※cssのクラス名の管理方法が適切かそうでないかは今回置いておきます。

Next.jsやVue.jsとの比較

そもそもLaravelはバックエンド言語だし、フロントの作りと比較も何も....という感じかもしれないのですが、書き方似てるよね!と感じたのでコンポーネントの作りを比較してみた。

比較するソースはサンプルで書いていたラジオボタンのコンポーネントファイル。

⚠️この比較内容と概念への注意事項

考え方などはあくまでも類似性のためそれぞれのフレームワークで概念は違うよっていうのがある。

Vue.jsでもComposition APIの導入でロジックを分離した書き方もできれば
Reactでも関数コンポーネントが主流でHooksの利用によって状態管理や副作用処理を行う方法が一般的と思うので、

一概に一緒ではなくてフロントエンドエンジニアでバックエンド初見な方向けへの「ざっくり大枠でなんか似てるかも!」レベルの比較のお話で書いていますのでご留意ください。

Laravel

ソースはすでに書いてあるので省略。
ポイントはこんな感じ↓

  • $attributes->mergeを使ってクラスを追加する
  • PHP構文で変数を埋め込む{{ $id }}形式

Next.js

ポイントはこんな感じ

  • TypeScriptの型定義でプロパティをがっちり固める
  • JSXで{id}のように変数を直接埋め込む
  • ...props でその他の属性も渡せるようにしてる

ポイントをまとめるとNext.jsの書き方は「クラスベースのコンポーネント」の考え方に近いかもしれない。

RadioButton.tsx
import React from 'react';

interface RadioButtonProps {
  id: string;
  name: string;
  value: string;
  checked?: boolean;
  label?: string;
  className?: string;
  onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
}

const RadioButton: React.FC<RadioButtonProps> = ({
  id,
  name,
  value,
  checked = false,
  label = '',
  className = '',
  onChange,
  ...props
}) => {
  return (
    <div className="radio">
      <input
        type="radio"
        id={id}
        name={name}
        value={value}
        checked={checked}
        onChange={onChange}
        className={`form-radio ${className}`}
        {...props}
      />
      <label htmlFor={id}>{label}</label>
    </div>
  );
};

export default RadioButton;

な....長い...

使用例
import RadioButton from './RadioButton';

export default function MyForm() {
  return (
    <form>
      <RadioButton 
        id="option1"
        name="options"
        value="option1"
        checked={true}
        label="オプション1"
        onChange={(e) => console.log(e.target.value)}
      />
      <RadioButton 
        id="option2"
        name="options"
        value="option2"
        label="オプション2"
      />
    </form>
  );
}

Vue.js

私はあまりvue.jsを使ったことがなく、あくまでもググったレベルの知見になってしまうので
参考のソースはララベルのサンプルソースを元にClaudeのお嬢様にお願いしました。

書いてもらったソースを見るとvueのコンポーネントのポイントは以下なのかなと思っていて

  • テンプレート構文で:id="id"のようにバインディング
  • v-bind="$attrs"v-on="$listeners"で追加の属性とイベントも渡せる

「Bladeコンポーネント」の考え方に近いかもしれない。
v-onの書き方とかx-onとかと一緒なのでは?という感じがする。

RadioButton.vue
<script setup>
defineProps({
  id: {
    type: String,
    required: true
  },
  name: {
    type: String,
    required: true
  },
  value: {
    type: String,
    required: true
  },
  checked: {
    type: Boolean,
    default: false
  },
  label: {
    type: String,
    default: ''
  }
});
</script>
使用例
<template>
  <form>
    <RadioButton
      id="option1"
      name="options"
      value="option1"
      :checked="true"
      label="オプション1"
      @change="handleChange"
    />
    <RadioButton
      id="option2"
      name="options"
      value="option2"
      label="オプション2"
    />
  </form>
</template>

<script>
import RadioButton from './RadioButton.vue';

export default {
  components: {
    RadioButton
  },
  methods: {
    handleChange(e) {
      console.log(e.target.value);
    }
  }
}
</script>

まとめ

今回初めてLaravelを触ったりPHPのソースを書いてみるということをしたので
付け焼き刃感は否めないのですが、フロントエンドでバックエンド言語の領域に入り込むステップとしてはコンポーネントを作るという機会は個人的によかったなと思いました。

フロントエンドでの考え方にも繋がってくるかと思うのですが
今回特に以下の3つが印象的でした

①Laravelでコンポーネントを作るときは
2タイプのうち、どっちで作るのかを決める(おすすめはBladeコンポーネント)
②propsでデフォルトの状態を作ると楽
③Vue.jsやNext.js触っていると体感的にコンポーネントは作りやすかった

学習サイト

Laravelで学んだことはまだ他にもあるのですが、楽しかったので学習サイトで学んでみようかなと思いました。

Claudeのお嬢様におすすめ教えてもらったうちの良さそうだなと思ったものを最後に共有します。
どちらも英語サイトなので読みづらいかもしれないのですがDeepLを活用しながら進めるのをおすすめします。

https://laracasts.com/
ドット絵の世界観が楽しい。
動画を見ながら色々作れる。Githubにソースある。
すでに環境が整ってる状態からスタートするので準備が大変。

https://laravel.com/docs/12.x
いわゆるララベルブートキャンプと呼ばれている公式のドキュメントファイル。

Discussion