Top View


Author Daiki Urata

LaravelにReact + TypeScriptを導入する  

2022/08/02

はじめに

前回はvite_railsをRailsに導入してReact/Vite環境を構築という記事を書いたので、次はLaravelにReact + TypeScript環境を構築してみようと思います。

Laravel 9ではViteがデフォルトで使えるのでReactを導入するだけで、あまりLaravel側を触らずに済むので良さそうです。

環境

  • Laravel 9
  • Vite 3
  • React 18

Laravelプロジェクト作成

まずはLaravelプロジェクトを Laravel Sail を使って作成します。

$ curl -s "https://laravel.build/example-app" | bash]
$ cd laravel-vite-app && ./vendor/bin/sail up

うまくいったらデフォルトはlocalhost:80 をブラウザで開くと最初の画面が見れると思います。

Reactの追加

次にLaravelプロジェクトにReactを導入します。

LaravelではデフォルトでVite環境が用意されているので割と簡単にできます。

Viteではテンプレートが用意されているのでそれをもとに導入していきます。

React以外にもVueなど様々なテンプレートが用意されているので詳しくは以下を参照してください。

Vueでも基本的にほぼ同じやり方になると思います。

$ cd resources
$ rm -rf js
$ npm create vite@latest react -- --template react-ts
$ cd react
$ rm index.html package.json vite.config.ts

ここではpackage.jsonやvite.config.tsなどLaravelプロジェクト作成時にすでにあるため、消しています。

次にプロジェクトルートにあるvite.config.jsをReactが使えるように設定を変えます。

今回はTypeScript環境なため、拡張子をvite.config.tsに変えています。

vite.config.js → vite.config.ts

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import react from '@vitejs/plugin-react'

export default defineConfig({
    plugins: [
        laravel([
            'resources/react/src/main.tsx',
        ]),
        react(),
    ],
});

laravel() のところではエントリポイントとしてresources/react/main.tsx を渡しています。

複数のViewで別のReactコンポーネントを呼び出したい場合は entrypoints のようなディレクトリを用意してそこにまとめるのも良いかもしれません。

今回は面倒だったので、そのままのmain.tsxを指定しています。

そしてpackge.jsonは以下のようになりました。

package.json

{
    "private": true,
    "scripts": {
        "dev": "vite",
        "build": "vite build"
    },
    "devDependencies": {
        "@types/react": "^18.0.15",
        "@types/react-dom": "^18.0.6",
        "@vitejs/plugin-react": "^2.0.0",
        "typescript": "^4.6.4",
        "axios": "^0.25",
        "laravel-vite-plugin": "^0.5.0",
        "lodash": "^4.17.19",
        "postcss": "^8.1.14",
        "react": "^18.2.0",
        "react-dom": "^18.2.0"
        "vite": "^3.0.0"
    }
}

これで設定周りは完了したので、パッケージのインストールをします。

$ ./vendor/bin/sail npm install

ここからはReactコンポーネントをLaravelから表示させてみます。

以下のApp.tsxを使います。

resources/react/src/App.tsx

import './App.css'

function App() {
  return (
    <div className="App">
      <h1>Vite + React + Laravel</h1>
    </div>
  )
}

export default App

すでにあるwelcome.blade.phpを編集して以下のようにします。

resources/view/welcome.blade.php

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Laravel</title>
        @viteReactRefresh
        @vite('resources/react/src/main.tsx')
    </head>
    <body>
        <div id="root"></div>
    </body>
</html>

@vite() ではエントリポイントとなるファイルを指定します。

@viteReactRefresh@vite() より先に読み込むようにしないとダメなようです。

これで表示する準備ができました。

Viteサーバーを起動して確認してみます。

$ ./vendor/bin/sail npm run dev

ブラウザでは以下のように表示されるはずです。

Laravel React Image 1

Controllerからデータを渡す

少し応用して今度はLaravelのControllerからデータをReactコンポーネントへ渡してみます。

まずはartisanコマンドでControllerを作成します。

$ ./vendor/bin/sail php artisan make:controller WelcomeController

作成されたWelcomeControllerではmessage という変数をViewに渡しています。

app/Http/Controllers/WelcomController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class WelcomeController extends Controller
{
    public function index()
    {
        return view('welcome', [
            'message' => "Hello, world!",
        ]);
    }
}

次にルーティングの設定をします。

routes/web.php

<?php

use App\Http\Controllers\WelcomeController;
use Illuminate\Support\Facades\Route;

Route::get('/', [WelcomeController::class, 'index']);

resources/views/welcome.blade.phpを少し変えてdata attributeに先ほど用意した変数を渡してReactコンポーネントまで伝えます。

resources/views/welcome.blade.php

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Laravel</title>
        @viteReactRefresh
        @vite('resources/react/src/main.tsx')
    </head>
    <body>
        <div id="root" data-message="{{ $message }}"></div>
    </body>
</html>

エントリポイントとなるmain.tsxでは渡ってきた変数をAppコンポーネントにそのまま流しています。

resources/react/src/main.tsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

const element = document.getElementById('root')

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <App {...element?.dataset}  />
  </React.StrictMode>
)

Appコンポーネントではpropsで渡ってきたmessage をそのまま表示しています。

resources/react/src/App.tsx

import './App.css'

type Props = {
    message?: string;
}

function App(props: Props) {
    return (
        <div className="App">
            {props.message}
        </div>
    )
}

export default App

これでブラウザを確認するとLaravelのControllerで用意したデータを表示することができました。

Laravel React Image 2

さいごに

Laravelではすでにフロントエンド環境が準備されてあるLaravel BreezeやLaravel Jetstreamなどのスターターキットがあります。

今回はスターターキットを使わずにフロントエンド環境を構築してみましたが、デフォルトでViteが使える状態なので、かなり簡単でした。

今回はできませんでしたが、次はInertiaでフロントエンドとバックエンドの連携を楽にできたらと思っていましたが、この記事を書いている2022年8月2日時点ではReact18対応がまだリリースされておらず試せませんでした。

Daiki Urata

Daiki Urata

Twitter X

フロントエンド/モバイルアプリなどを主に開発しています。