LoginSignup
0

More than 1 year has passed since last update.

【感謝記念】Phoenix 1.4でElixirしか書いてないが React的SPA/リアルタイムUIが作れる「LiveView」(APIとJavaScriptは書いていない)

Last updated at Posted at 2022-01-08

下記Elixirコミュニティ運営/所属の piacere です、ご覧いただいてありがとございます :bow:

Elixir実装の芽を愛でるコミュニティ「Elixir Digitalization Implementors」
福岡Elixirコミュニティ「fukuoka.ex」
小倉Elixirコミュニティ「kokura.ex」

ElixirでリアルタイムUIを作るLiveView/Livebookで盛り上がるコミュニティ「LiveView JP」

Phoniex 1.5以降、React的SPA/リアルタイムフロントをElixirサーバサイドのみで書ける「LiveView」が標準搭載され、Phoenix 1.4までで必要だったLiveView各種設定が不要となったのですが、本設定が凄まじいボリュームだったので、それが今では不要となった功績に感謝しつつ、記念として残しておくとしました

Phoenix 1.5以降をご利用の方は、ここに書いている手順を一切すること無く、LiveViewが利用可能ですので、下記コラムからお試しください

内容が、面白かったり、役に立ったら、「LGTM」よろしくお願いします :wink:

本コラムの検証環境、事前構築のコマンド

本コラムは、以下環境で検証しています(Windowsで実施していますが、Linuxやmacでも動作する想定です)

LiveViewはこんなリアルタイム検索アプリが開発できます

Phoenix 1.4でのLiveViewセッティングは、LiveViewがリリースされたばかりのため、割と手順が多く、途中で心が折れてしまうかも知れないため、完成したときに、何ができるかを先に動画で紹介しておきます

サーバサイドで書かれたコードにも関わらず、ReactやVue.jsのようなリアルタイムUIが構築できます … 反応速度もスピーディで、もたつく感じは一切無く、ReactやVue.jsで書いたのと変わらないフィーリングです

下記画像をクリックしてご覧ください
image.png

LiveView向けPhoenix PJの作成

LiveViewを動かすためのPhoenix PJを作成します

mix phx.new basic --no-ecto
 …(ファイル作成ログが続く)…
Fetch and install dependencies? [Yn] 【nを入力し、Enter】
cd basic

LiveViewとSmallexのインストール

ライブラリとして、LiveView(phoenix_live_view)を追加します

あと、LiveViewのサンプルとして、外部API呼出をするので、smallexも追加します

mix.exs
defmodule LvSample.MixProject do

  defp deps do
    [
      {:phoenix_live_view, github: "phoenixframework/phoenix_live_view"},  # <- add here
      {:smallex, "~> 0.0"},  # <- add here
      {phoenix, "~> 1.4.2"},
      

ライブラリをインストールします

mix deps.get

Phoenix 1.4におけるLiveView環境の設定

①セッション用saltの設定

LiveViewが利用するセッションをハッシュするためのsaltを設定します

まず、32バイトのsaltを生成します

mix phx.gen.secret 32
Gduxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

上記で生成した32バイトsaltを、下記の通り、LiveView向けに設定します

config/config.exs
# This file is responsible for configuring your application

config :lv_sample, LvSampleWeb.Endpoint,
  live_view: [ signing_salt: "Gduxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ], 
  # ^--- add here
  url: [host: "localhost"],
  secret_key_base: "AcPkxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  

②ビューとルータのマクロ設定

ビューとルータのマクロに、LiveView用の設定を追加します

【2020/03/09追加】
LiveView 0.5以降では、「def controller do」配下への追加が増え、「def view do」の追加内容が変わりました

lib/lv_sample_web.ex
defmodule LvSampleWeb do

  def controller do
    quote do
      use Phoenix.Controller, namespace: LvSampleWeb

      import Plug.Conn
      import LvSampleWeb.Gettext
      alias  LvSampleWeb.Router.Helpers, as: Routes
      import Phoenix.LiveView.Controller  # <- add here
    end
  end

  def view do
    quote do
    
      import LvSampleWeb.ErrorHelpers
      import LvSampleWeb.Gettext
      alias  LvSampleWeb.Router.Helpers, as: Routes
      import Phoenix.LiveView.Helpers  # <- add here
    end
  end

  def router do
    quote do
      use Phoenix.Router
      import Plug.Conn
      import Phoenix.Controller
      import Phoenix.LiveView.Router  # <- add here
    end
  end
  

③LiveViewファイルのための設定

LiveViewファイルを置くためのフォルダを作成します

mkdir lib/lv_sample_web/live

LiveViewファイルが更新された際のLiveReloadを2箇所、設定します

config/dev.exs
use Mix.Config

config :lv_sample, LvSampleWeb.Endpoint,
  live_reload: [
    patterns: [
      ~r{lib/lv_sample_web/live/.*(ex)$},  # <- add here
      ~r{lib/lv_sample_web/templates/.*(leex)$},  # <- add here
      ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
      ~r{priv/gettext/.*(po)$},
      

④LiveView用エンドポイントの追加

LiveView用のエンドポイントとして、「/live」を追加します

【2020/03/09追加】
LiveView 0.5以降では、Plug.Session情報をエンドポイントに設定することができます(下記ではコメントアウトしています)

lib/lv_sample_web/endpoint.ex
defmodule LvSampleWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :lv_sample

// v- add here
#  @session_options [
#    store: :cookie,
#    key: "_lv_sample_key",
#    signing_salt: "xxxxxxxxx"
#  ]
#  plug Plug.Session, @session_options
#  socket "/live", Phoenix.LiveView.Socket, websocket: [ connect_info: [ session: @session_options ] ]
  socket "/live", Phoenix.LiveView.Socket, websocket: true
// ^- add here
    

⑤JavaScript側のLiveViewソケット接続設定の追加

app.jsに、「phoenix_live_view」をインポートし、「/live」にソケット接続するための設定を追加します

【2020/03/09追加】
LiveView 0.5以降では、CSRFトークンをLiveSocketのパラメータとして指定することができます(下記ではコメントアウトしています)

assets/js/app.js
// We need to import the CSS so that webpack will load it.

// v- add here
import { Socket } from "phoenix"
import LiveSocket from "phoenix_live_view"

let liveSocket = new LiveSocket( "/live" )
//let csrfToken = document.querySelector( "meta[name='csrf-token']" ).getAttribute( "content" )
//let liveSocket = new LiveSocket( "/live", Socket, { params: { _csrf_token: csrfToken } } )
liveSocket.connect()
// ^- add here
lib/lv_sample_web/templates/layout/app.html.eex
<head>
  <%= csrf_meta_tag() %>

⑥Nodeモジュールのインストール

package.jsonの「dependencies」に、「phoenix_live_view」のエントリー(下記dependenciesの直下1行)を追加します

assets/package.json
{
  "dependencies": {
    "phoenix_live_view": "file:../deps/phoenix_live_view",
    "phoenix": "file:../deps/phoenix",
    "phoenix_html": "file:../deps/phoenix_html"
  },
  

assets配下で、Nodeモジュールのインストールを行います

【2020/03/09追加】
LiveView用WebSocketが開かないことがあるため、Phoenix関連モジュールの強制アップデートをします

cd assets
npm install --force phoenix_live_view phoenix_html phoenix
cd ..

LiveViewサンプルの作成

さぁ、LiveView環境が準備できたので、リアルタイムにQiitaを検索するSPAを作ってみます

検索用の入力フィールドをフロントUIに持ち、LiveViewの機能でサーバ側に自動連携させ、入力が変わるたびにサーバからQiitaを検索し、その検索結果をフロントに反映させます

そのコードは、以下の通りです

【2020/03/09追加】
LiveView 0.6以降では、mountの引数が、2つから3つに変更(第1引数paramsが追加)されました

lib/basic_web/live/qiita_search_realtime.ex
defmodule BasicWeb.QiitaSearchRealtime do
  use Phoenix.LiveView

  def render( assigns ) do
    ~L"""
      <p>
      <%= if @message do %><%= @message %><% end %>
      </p>

      <form phx-change="change">
      <input type="text" name="query" value="<%= @query %>" placeholder="empty" />
      Query: <%= @query %><br>
      </form>

      <table>
      <tr>
        <th>ID</th>
        <th>タイトル</th>
        <th>作成日</th>
      </tr>
      <%= for result <- @results do %>
      <tr>
        <td><%= result[ "id" ] %></td>
        <td><%= result[ "title" ] %></td>
        <td><%= result[ "created_at" ] %></td>
      </tr>
      <% end %>
      </table>
    """
  end

  def mount( _params, _session, socket ) do
    { :ok, assign( socket, query: "", message: "[Init]", results: [] ) }
  end

  def handle_event( "change", %{ "query" => query }, socket ) do
    send( self(), { :submit, query } )
    { :noreply, assign( socket, message: "[Searching...]", query: query ) }
  end

  def handle_info( { :submit, query }, socket ) do
    results = Json.get( "https://qiita.com", "/api/v2/items?query=#{ query }" )
    { :noreply, assign( socket, message: "[Complete!!]", results: results ) }
  end
end

①LiveViewサンプルにおける初期画面表示

LiveViewのコードは、大枠としては、画面初期時に、render()とmount()が呼ばれ、render()に配置されたパーツが、mount()でassignされた内容で画面が初期化されます

mount()は、Vue.jsで言うところの「mounted」に相当します

③LiveViewサンプルにおけるハンドラ

その後は、render()で配置されたパーツ毎に設定されたハンドラが動きます

たとえば、「phx-change」を使うことで、入力フィールドの変更があるたびに、handle_event()が呼び出されます

ここは、Vue.jsで言うところの「v-on:change」に相当する訳ですが、Vue.jsとの違いは、フロントサイドの関数を呼ぶのでは無く、サーバサイドの関数が呼ばれる点です

③LiveViewサンプルにおけるハンドラ内処理

handle_event()に渡される引数であるqueryは、フロントUIの入力フィールドに入力された値がサーバ側に渡されており、サーバサイドで処理が進められます

その中では、メッセージパッシングであるsend()でhandle_info()を呼び出し、その裏で、messageの更新を行っています

そして、サーバサイドで行われたmessageの更新は、フロントUI上のメッセージフィールドに反映されます

handle_info()では、Qiita APIを呼び出し、その結果をresultsに格納し、messageを更新します

LiveView最大の特徴:フロントとサーバの通信は記述不要

handle_event()/handle_info()共に、呼び出された時点で、フロントサイドの入力値や状態が、サーバサイドに渡され、以降の処理はサーバサイドで実行され、その結果をフロントサイドに反映できる…これを、サーバサイドのコードを書くだけで実現できるのが、LiveViewの最大の特徴です

これをVue.js等で開発した場合、サーバサイドにAPIを構築し、フロントサイドからAPIを呼び出す…といった手間がかかるところ、LiveViewは、通常のElixirコードをサーバサイドで書くだけで、API無にフロントサイドとサーバサイドのデータバインディングが可能です

機能規模のあるフロントを開発すると、APIがすぐに何十本、何百本と増え、管理が煩雑になるところを、LiveViewは、フロントとサーバの通信を明示的に書く必要が無く、あくまでサーバサイドのコードだけで表現できるため、非常にシンプルにまとまります

LiveView用plug、レイアウト外枠、ルーティングの追加

さて、SPAも書けたので、後はルーティングを設定したら、実行できるようになります

router.exの「pipeline :browser」中にある、「plug :fetch_flash」の直後に 「plug Phoenix.LiveView.Flash」 「:fetch_live_flash」を追加し、LiveView用のレイアウト外枠を設定し、「/realtime」でLiveView専用のルーティングを追加します

【2020/03/09追加】
LiveView 0.8以降では、「Phoenix.LiveView.Flash」の代わりに「:fetch_live_flash」の追加が必要になり、「plug :fetch_flash」はコメントアウトが必要です

次に、「:put_layout」 「:put_live_layout」「:put_root_layout」で、レイアウト外枠として「templates/layout/app.html.eex」を指定します(このレイアウト外枠から、/js/app.js中のLiveView用WebSocket接続が呼び出されます)

【2020/03/09追加】
LiveView 0.8以降では、「:put_layout」による指定が効かなくなったので、「:put_live_layout」でレイアウト外枠を指定する必要があります

【2020/04/09追加】
LiveView 0.11以降では、「:put_live_layout」による指定が効かなくなったので、「:put_root_layout」でレイアウト外枠を指定する必要があります

最後に、通常のコントローラだと、「resource」や「get」と、コントローラのモジュール名+関数を指定しますが、LiveViewでは、「live」と、LiveViewモジュール名を指定します

lib/basic_web/router.ex
defmodule BasicWeb.Router do
  use BasicWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, {BasicWeb.LayoutView, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end
  

  scope "/", BasicWeb do
    pipe_through :browser
    
    live "/realtime", QiitaSearchRealtime  # <- add here
    get "/", PageController, :index
  end

Phoenix起動、LiveViewサンプル実行

iex -S mix phx.server

ブラウザで「http://localhost:4000/realtime」にアクセスすると、こんな感じのQiita検索SPA画面が表示されます
image.png

入力フィールドに文字を入れると、入力フィールド下部に入力した文字列がリアルタイム更新され、入力フィールド上部には「[searching...]」と検索中であるメッセージが表示され、裏では、Qiitaへの検索が走り始めます
image.png

少しすると、メッセージが「[complete!!]」に変わり、画面下部に検索結果が反映されます
image.png

実際に動いている動画もアップしたので、コチラをご覧いただくと、より実感が沸くかと思います

けっこう反応速度がスピーディで、もたつく感じは一切ありません(Vue.js等でフロントネイティブで書いているフィーリングと、ほぼ変わらないです)
image.png

終わり

今回は、LiveView環境を構築して、リアルタイムにQiita検索を行うSPAを作りました

サーバサイドのElixirコードを書くだけで、フロントUIのリアルタイム操作/反映が可能であり、SPAを始めとするWebアプリを作るための強力過ぎる土台だという感触が伝わったでしょうか?

リリース前から、かなりの可能性を感じていたLiveViewですが、実際に動かしてみて、Vue.js等とほぼ変わらない速度やフィーリングで、しかもフロントとサーバの書き分けが一切要らず、入力内容やデータ状態をAPI構築無かつサーバサイドで一元管理できる…なんだか魔法のような体験でした

次回は、「LiveViewにおけるsubmitボタンの制御」について解説します

p.s.「いいね」よろしくお願いします

ページ左上の image.pngimage.png のクリックを、どうぞよろしくお願いします:bow:
ここの数字が増えると、書き手としては「ウケている」という感覚が得られ、連載を更に進化させていくモチベーションになりますので、もっとElixirネタを見たいというあなた、私達と一緒に盛り上げてください!:tada:

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0