【laravel】laravel+Reactを使ってSPAにしてみる

初書：2021/09/06
mac : 11.5.2
php：v8.0.6
laravel：v8.57.0

# 前書き

最近Reactを知ったのでせっかくならLaravelでも使ってみよう、というメモ。

# 前提

Laravelインストール済み。
[素のPHPしか触ってなかった人がlaravelを触ってみる - Qiita](https://qiita.com/yuu_1st/items/6e8c8b74478de37918eb)

# インストール

まずはuiをインストール

```zsh
% composer require laravel/ui
```

その後はuiをreactに変更

```zsh
% php artisan ui react
```

インストールとmix

```zsh
% npm install && npm run dev
```

もし、`Please run Mix again.`と言われた場合はもう一度`npm run dev`します。

以上

# Typescriptにする

ReactといえばTypescript…と、勝手に思ってるので、Typescriptで書けるように追加していく。

## インストール

まずは必要なものをインストール

```zsh
% npm install --save-dev ts-loader typescript react-router-dom @types/react @types/react-dom @types/react-router-dom
```

`react-router-dom`と`@types/react-router-dom`はSPAを作る場合。laravel側でルーティングする場合は不要。
でもこの記事では使います。

## tsconfig.jsonの設定

```zsh
% npx tsc --init
```
これでtsconfig.jsonが生成される。
設定は好みだが、一応今設定しているものを置いておく。

```json-doc:tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "lib": [
      "ES2020",
      "DOM",
    ],
    "jsx": "react-jsx",
    "module": "AMD",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": [
    "resources/ts/**/*"
  ]
}
```

`target`は`ES2020`を選択。頻繁なアプデを好まない層(IEとか古いiOSとか)がこないサイトなら最新のものでいいと思う。
`module`は`AMD`を選択した。es2020とかとの違いはあんまり分かってない。(非同期で読み込める点？でものちに1ファイルにまとめる・・・。)

各ブラウザの対応状況：[ECMAScript 2016+ compatibility table](https://kangax.github.io/compat-table/es2016plus/)

# ページを作成する

今回はメインページとサブページを作る。
laravelでtsを置くのは基本的にresourcesディレクトリなので、この下に作成する。

```tsx:resources/ts/app.tsx
import ReactDOM from "react-dom";
import { BrowserRouter, Route, Switch } from "react-router-dom";

import main from "./main";
import sub from "./sub";

function App() : JSX.Element {
    return (
        <BrowserRouter>
            <Switch>
                <Route exact path="/" component={main} />
                <Route exact path="/sub" component={sub} />
            </Switch>
        </BrowserRouter>
    );
}

ReactDOM.render(<App />, document.getElementById("root"));
```

```tsx:resources/ts/main.tsx
import React from "react";
import { Link } from "react-router-dom";

const element = (): JSX.Element => {
  return (
    <div>
      <p>メインページです。</p>
      <Link to="/sub">サブページへ</Link>
    </div>
  );
};

export default element;
```

```tsx:resources/ts/sub.tsx
import React from "react";

const element = (): JSX.Element => {
    return <p>サブページです。</p>;
};

export default element;
```

React、最近は関数型の書き方の方が良いって聞いたので関数型を使ってみた。

基盤となる`app.tsx`とページを表示するmainとsubを作成。
これが出来れば、それを表示するための`welcome.blade.php`を変更する

```php: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>
    </head>
    <body class="antialiased">
        <div id="root">
        </div>
    </body>
        <script src="{{ mix('js/app.js') }}"></script>
</html>
```

表示用のdivと、読み込みスクリプトがあれば後は自由。
ページは以上

# laravel mixを使う

そのままでは`app.js`なるファイルは存在していないので、作成する。
普通にインストールしていると、`webpack.mix.js`が既に存在しているので、これを編集する。

```js:webpack.mix.js
const mix = require('laravel-mix');

mix.js('resources/ts/app.tsx', 'public/js')
    .react()
    .webpackConfig({
        module: {
            rules: [
                {
                    test: /\.tsx?$/,
                    loader: 'ts-loader',
                    exclude: /node_modules/,
                },
            ],
        },
        resolve: {
            extensions: ['*', '.js', '.jsx', '.ts', '.tsx'],
        },
   })
   .version();
```

`react()`が何するのかよく分かってない。誰か教えてください(他人頼り)

これで、`npm run dev`を実行すると、先ほど作った`app.tsx`関連が`app.js`にまとめられる。

# 一度アクセスしてみる

これで一度アクセスしてみる。
`php artisan serve`でサーバーを立て、`http://127.0.0.1:8000/`にアクセスすると、先程の`メインページです`が表示されているはず。
また、サブページへを押すと、`http://127.0.0.1:8000/sub`にアクセスされ、`サブページです`が表示される。

一見これでいいように見えるが、直接`http://127.0.0.1:8000/sub`にアクセスすると、laravel側からurlが存在していないと言われる。
これは、subページがReact内だけで作成されているだけのため。

この先は仕様次第だが、今回はReact側でアクセス処理を任せることにする

# 全てのアクセスをapp.tsxにまとめる

アクセスは`routes/web.php`で管理しているので、ここを変える

```php:routes/web.php
Route::get('/{any}', function () {
    return view('welcome');
})->where('any', '.*');
```

元々あった`/`を`/{any}`に変え、条件を全てに変更した。
where文があるのは、`/`も`any`に含まれるようにするため。

これで、直接`http://127.0.0.1:8000/sub`にアクセスしても、`サブページです`が表示される。

# 終わりに

この後ブラウザとサーバー側でデータのやりとりをするため、APIの設定等をしないといけないが、長くなりそうなので今回は一旦ここで切り上げ。
まだ試行錯誤段階なので、やり方異なってたり、よりいい方法とかあれば更新する…かも。


# 参考サイト

[React.JS + Laravel 8 + MySQL + API REST でCRUDを作ってみた。](https://leben.mobi/blog/laravel_raect_crud/php/)

[【環境構築】LaravelでReactとTypescriptを使う方法 - Qiita](https://qiita.com/mineaki27th/items/ad774a41b7a0a68761bd)

[【Typescript×React】tsconfig.jsonの設定項目を詳しく紹介 - Qiita](https://qiita.com/shiei_kawa/items/91a79461afa1b1549f13)

[laravel-mixでtypescript + reactをビルドする - Qiita](https://qiita.com/GHKEN/items/05fd89c75bce6718c434)