Edited at

LiveViewでSPA開発①: Elixirのみでフロントのリアルタイム入力/反映するSPAを実現(APIとJavaScriptは書いていない)

fukuoka.ex代表のpiacereです

ご覧いただいて、ありがとうございます :bow:

2019/3/15(金)、昨年からずっと待ち続けてたPhoenix LiveViewが正式リリースされたので、早速、試してみました

通常のPhoenix PJを、LiveView化した後、「Excelから関数型言語マスター」4回目で作ったQiita検索UIに、リアルタイム検索を追加したSPAとして構築し直す手順を解説します

なお、「Phoenix」は、ElixirのWebフレームワークです

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


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

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


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

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

image.png

サーバサイドで書かれたコードにも関わらず、反応速度がスピーディで、もたつく感じは一切ありません(Vue.js等で書かれたフロントと同じフィーリングです)


LiveViewを試すためのPhoenix PJの作成

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

mix phx.new lv_sample --no-ecto

 …(ファイル作成ログが続く)…
Fetch and install dependencies? [Yn] (←n、Enterを入力)
cd lv_sample


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"},
{:phoenix_pubsub, "~> 1.1"},


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

mix deps.get


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


lib/lv_sample_web.ex

defmodule LvSampleWeb do


def view do
quote do

import LvSampleWeb.ErrorHelpers
import LvSampleWeb.Gettext
alias LvSampleWeb.Router.Helpers, as: Routes
import Phoenix.LiveView, only: [live_render: 2, live_render: 3] # <- 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」を追加します


lib/lv_sample_web/endpoint.ex

defmodule LvSampleWeb.Endpoint do

use Phoenix.Endpoint, otp_app: :lv_sample

socket "/live", Phoenix.LiveView.Socket # <- add here

socket "/socket", LvSampleWeb.UserSocket,
websocket: true,



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

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


assets/js/app.js

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


// v- add here
import LiveSocket from "phoenix_live_view"

let liveSocket = new LiveSocket( "/live" )
liveSocket.connect()
// ^- add here



⑥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モジュールのインストールを行います

cd assets

npm install


LiveViewサンプルの作成

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

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

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


lib/lv_sample_web/live/qiita_search_realtime.ex

defmodule LvSampleWeb.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( _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」を追加し、LiveView用のレイアウト外枠を設定し、「/realtime」でLiveView専用のルーティングを追加します

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


lib/lv_sample_web/router.ex

defmodule LvSampleWeb.Router do

use LvSampleWeb, :router

pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug Phoenix.LiveView.Flash # <- add here
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :put_layout, { LvSampleWeb.LayoutView, :app } # <- add here

scope "/", LvSampleWeb do
pipe_through :browser

live "/realtime", QiitaSearchRealtime
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: