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

下記Elixirコミュニティ運営／所属の [piacere](https://twitter.com/piacere_ex) です、ご覧いただいてありがとございます :bow:

[**Elixir実装の芽を愛でるコミュニティ「Elixir Digitalization Implementors」
福岡Elixirコミュニティ「fukuoka.ex」
小倉Elixirコミュニティ「kokura.ex」**](https://fukuokaex.connpass.com)

https://fukuokaex.connpass.com?1

[**ElixirでリアルタイムUIを作るLiveView／Livebookで盛り上がるコミュニティ「LiveView JP」**](https://liveviewjp.connpass.com)

https://liveviewjp.connpass.com

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

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

https://qiita.com/piacerex/items/3f8ee18c9443d63955bf

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

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

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

- Windows 10
- Elixir 1.8.0　　 ※<b>[最新版のインストール手順はコチラ](https://qiita.com/piacere_ex/items/6714e1440e3f25fb46a1#%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99elixir%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)</b>
- Phoenix 1.4.2　※<b>[最新版のインストール手順はコチラ](https://qiita.com/piacere_ex/items/b7787580fce5f148242f#phoenix%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)</b>

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

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

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

下記画像をクリックしてご覧ください
[![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/279428b1-60b7-d180-8298-fed398cf2316.png)](https://twitter.com/piacere_ex/status/1108090347022778368)

# 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も追加します

```elixir: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向けに設定します

```elixir: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用の設定を追加します

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

```elixir: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箇所、設定します

```elixir: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」を追加します

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

```elixir: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」にソケット接続するための設定を追加します

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

```js: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
```

```erb:lib/lv_sample_web/templates/layout/app.html.eex
…
  <head>
  <%= csrf_meta_tag() %>
  …
```

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

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

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

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

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

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

# LiveViewサンプルの作成

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

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

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

<font color="red">【2020/03/09追加】
LiveView 0.6以降では、mountの引数が、2つから3つに変更（第1引数paramsが追加）されました</font>

```elixir: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」の直後に <strike>「plug Phoenix.LiveView.Flash」</strike> 「:fetch_live_flash」を追加し、LiveView用のレイアウト外枠を設定し、「/realtime」でLiveView専用のルーティングを追加します

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

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

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

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

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

```elixir: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](https://qiita-image-store.s3.amazonaws.com/0/155423/33489034-b91b-3f4a-4bfd-df155a83cc25.png)

入力フィールドに文字を入れると、入力フィールド下部に入力した文字列がリアルタイム更新され、入力フィールド上部には「[searching...]」と検索中であるメッセージが表示され、裏では、Qiitaへの検索が走り始めます
![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/17c412b7-746e-8d25-fb26-0c1b6e08c25c.png)

少しすると、メッセージが「[complete!!]」に変わり、画面下部に検索結果が反映されます
![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/2ce69f89-4a64-9325-a51c-bf3423546a16.png)

[実際に動いている動画もアップ](https://twitter.com/piacere_ex/status/1108090347022778368)したので、コチラをご覧いただくと、より実感が沸くかと思います

けっこう反応速度がスピーディで、もたつく感じは一切ありません（Vue.js等でフロントネイティブで書いているフィーリングと、ほぼ変わらないです）
[![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/279428b1-60b7-d180-8298-fed398cf2316.png)](https://twitter.com/piacere_ex/status/1108090347022778368)

# 終わり

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

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

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

[次回は、<b>「LiveViewにおけるsubmitボタンの制御」</b>](https://qiita.com/piacere_ex/items/21b0e308a36e486d8b25)について解説します

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

ページ左上の ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/4d515047-cc48-382e-c2b1-3ad0cc50dbbf.png) や ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/a4e3da58-70a3-4197-95a2-6a6906650d01.png) のクリックを、どうぞよろしくお願いします:bow:
ここの数字が増えると、書き手としては「ウケている」という感覚が得られ、連載を更に進化させていくモチベーションになりますので、もっとElixirネタを見たいというあなた、私達と一緒に盛り上げてください！:tada: