🖋

続・PhoenixでReactを使う ~Router編~

2020/11/08に公開

誰に向けた記事か

この記事は前回の記事(PhoenixでReactを使う)の続編です.前回の記事でカバーしきれなかったRouterの設定について紹介します.したがって,対象者は前回と同じです.

  • 普段elixir(Phoenix)でサーバーサイドの開発をしている
  • フロントエンドの知識が全くないが,フロントを書かなくてはいけない
  • しかし,種々の理由でサーバーサイドレンダリングを使えないor使いたくない(アニメーションが多用されるページとか?)

Phoenix バージョン

$ mix phx.new -v
Phoenix v1.5.5

まえがき

前回の記事ではPhoenixで書いたアプリのフロントをReactで構成する方法を紹介しました.しかし前回の記事で紹介した方法では,シングルページしか対応出来ておらず,routerはphoenixのものを使用したままになっていました.小規模なアプリならこれでもOKですが,小規模であればそもそもReactいらなくね?という話にもなってくるので,react-routerを使ってページ遷移をできるように調査をしました.

この記事の内容は既にボイラープレートにマージ済みです.

やりかた

前回分の設定が完了している前提での解説です.まだ設定していない場合はこちらを参考に先に設定をしてください.

node_moduleのインストール

cd assets
npm install --save react-router react-router-dom
npm install --save @types/react-router @types/react-router-dom   # typescriptを使用する場合

Routerに対応したスクリプトの準備

以前と少し設定が変わっているため,typescriptでの実装になっています.jsで実装する場合は,前回の記事のassets/js/app.jsを書き換えてください.typescriptに対応させたい場合は,特にphoenix側で設定することはないので,assets内でtypescript用のモジュールをインストールし,webpack.config.jsを書き換えてください.

assets/src/index.txs
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom';

const MyMain: React.FunctionComponent<{}> = () => {
  return (
    <div>This is a root page</div>
  );
}

const MySecond: React.FunctionComponent<{}> = () => {
  return (
    <div>This is a second page</div>
  );
}

const Root: React.FunctionComponent<{}> = () => {
  return (
    <Switch>
      <Route exact path="/" component={MyMain} />
      <Route exact path="/second" component={MySecond} />
      <Redirect to="/" />
    </Switch>
  );
}

ReactDOM.render(
  <BrowserRouter>
    <Root />
  </BrowserRouter>,
  document.getElementById('root')
);

Rootのパラメータにexactが渡してあるのですが,これはパスに完全マッチさせるために必要なオプションなので,exactなしだと意図したとおりにページが表示されてくれません.(フロントに慣れている方だと当り前なんでしょうね・・・僕は知らなかったので結構沼りました)

routerの設定

phoenixでフロントもAPIサーバーも提供する場合,フロントのページへアクセスしようとした場合,必ずlib/project_web/router.exを通ることになります.なので,route制御をReact側でやりたい場合にはphoenixのrouterからフロント側にフォワーディングしてやる必要があります.

phoenix forwardingあたりでググると,plugの解説がいくつもヒットしますが,plugでやる必要はないです
(最初plugで実装する必要があるのかと思ってました)

特定のパス以下を全てフォワーディングしたい場合には以下のようにrouter.exを書き換えます.

lib/project_web/router.ex
  scope "/", ReviveWeb do
    pipe_through :browser

    get "/*path", PageController, :index
  end

このように設定した場合,http://localhost:8000/something.htmlにアクセスすると,PageController.index/2parameters%{path => "something.html"}が渡されます.つまり,router.exのパスでアスタリスクの後に続けられたキーにパスがマッチされます.

既に前章で//secondにページを作ってあるので,mix phx.serverでサーバーを立ち上げると,http://localhost:8000/にアクセスすればThis is a root pagehttp://localhost:8000/second/にアクセスすればThis is a second pageと表示されます.

phoenixのテンプレートプロジェクトだと,dashboardのルーティングがデフォルトで定義されています.これよりも前に上記のルーティングを定義してしまうとwarningが出てしまいます.
router.exの定義順序は所詮elixirのマッチングで処理されるので,ドメインのルート以下全てをマッチングさせたい場合は一番下に持ってくると良いかと思います.

こんな感じですね.

lib/project_web/router.ex
  if Mix.env() in [:dev, :test] do
    import Phoenix.LiveDashboard.Router

    scope "/" do
      pipe_through :browser
      live_dashboard "/dashboard", metrics: ReviveWeb.Telemetry
    end
  end

  scope "/", ReviveWeb do
    pipe_through :browser

    get "/*path", PageController, :index
  end

あとがき

これで一通り必要な物が揃ったのかなと思っています.まだLintやらなんやら必要なパッケージはある気がしますが,フロント側で完結できる設定しか残っていないはずなので,検索すれば出てくる情報ばかりになるのかなと.
少し気になっている点としては,react-nativeを利用する場合にはどうなるのかですが,こちらは僕がまだreact-nativeがどのように動くのかすら全く理解してないので,必要になればそのうち書くかも知れません.

参考にさせてもらった記事

Discussion