【本コラムは、5分で読めて、10分くらいでお試しいただけます】
piacereです、ご覧いただいてありがとございます
今回は、「ExcelからElixir入門④:Webで外部API表示」で作ったQiita検索UIを、LiveViewでリアルタイム検索可能なSPAとして構築し直す手順を解説します
本コラムは、さまざまな時期のElixir/Phoenix環境で検証済みです
■「ExcelからElixir入門」シリーズの目次
①データ並替え/絞り込み
|> ②データ列抽出、Web表示
|> ③WebにDBデータ表示
|> ④Webに外部APIデータ表示
|> ⑤Webにグラフ表示
|> ⑥SPAからPhoenix製APIを呼び出す(表示編)【LiveView版】
|> ⑦SPAからPhoenix製APIを呼び出す(更新編)【LiveView版】
|> ⑧Gigalixirに本番リリース
|> ⑨ElixirサーバサイドのみでReactと同じSPA/リアルタイムUIが作れる「LiveView」
|> ⑩ElixirサーバサイドSPAをスマホで見るためにGigalixirリリース
|> ⑪Gigalixir上のLiveViewアプリに独自ドメイン名を付与して正式なアプリ公開
|> ⑫Elixir/PhoenixのCRUD Webアプリをリリース
LiveView:リアルタイムSPAをサーバサイドのみで開発可
Elixirサーバサイドのみで書かれたコードにも関わらず、Vue.jsやReactのようなリアルタイムSPAが構築できるのが「LiveView」です
Vue.jsやReactとほぼ変わらない反応速度やフィーリングで、しかもフロントとサーバの書き分けが一切要らず、フロント側の入力内容やデータ状態をサーバサイドで一元管理でき、しかもAPI構築要らず … なんだか魔法のような体験ができるLiveViewをお楽しみください
LiveView最大の利点:API構築とフロント通信記述が不要
ReactやVue.jsでSPA開発した場合、サーバ側にAPIを構築し、フロント側からaxios等でAPIを呼び出す必要がありますが、これは手間がかかるし、フロント側で状態管理もしなければいけません
更に、APIがすぐに何十本、何百本と増え、管理が煩雑になりやすいのも「SPA開発あるある」です
近年はBFF(Backend For Frontend)でフロントサイドはシンプル化できるものの、サーバサイドはAPIからAPIを呼ぶ構造となるため、性能は劣化しやすく、APIの本数自体が減っている訳ではありません
LiveViewは、API無にフロントとサーバのデータバインディングが可能(しかもリアルタイム)なので、API不要かつフロントからサーバへのAPI呼出処理を書く必要が無く、あくまでElixirサーバサイドのコードだけでSPAを構築できるのがLiveView最大の利点です(BFF構築もElixirサーバサイド内のリファクタリングだけとなります)
「Qiita検索SPA」をLiveViewで開発する
それでは早速、以下2バージョンのQiita検索SPAをLiveViewで構築してみましょう
- ①検索キーワード入力されるたびにリアルタイム検索
- ②「検索」ボタン押下時のみ検索(入力確認のみ反映)
その土台となるPhoenix PJ作成/Req導入/Phoenix起動を下記3つで行ってから、この先をお試しください(このシリーズコラムを最初からお試しいただいている方は不要です)
mix phx.new basic --no-ecto
cd basic
defmodule Basic.Mixfile do
use Mix.Project
…
defp deps do
[
+ {:req, "~> 0.4"},
{:phoenix, "~> 1.7.7"},
…
]
end
…
mix deps.get
iex -S mix phx.server
なお時間に余裕があって、ElixirやPhoenixを最新化してから試したい方は、下記を先に実施しておいてください(タイトルはUbuntuと入っていますが、MacやWindowsでも大丈夫な手順です)
①検索キーワード入力されるたびにリアルタイム検索
最初は、検索キーワードが入力されるたびにQiita APIを呼び出し、リアルタイムで検索した結果を画面にリアルタイム反映する、SPAらしいアプリを作ります
リアルタイム入力を元にQiita検索を行う
検索フィールドの内容が変わるたびに、サーバから「Req」でQiitaを検索し、その検索結果をフロントに自動反映させます
LiveViewモジュールは、初期時の mount
と、検索キーワード入力のたびに呼ばれる handle_event("onChange" ~
、あとQiita APIを呼び出して検索を行う handle_info({"onSearch" ~
の3つのハンドラを用意します
なお、コード内容は、LiveViewに関する部分は第6回、Qiita APIによる検索は第4回にて解説済みなので、忘れてしまった方は各リンクを見返してください
defmodule BasicWeb.QiitaLive do
use BasicWeb, :live_view
def mount(_params, _session, socket) do
{:ok, assign(socket, [query: "", datas: [], message: "[Init]"])}
end
def handle_event("onChange", %{"query" => query}, socket) do
send(self(), {"onSearch", query})
{:noreply, assign(socket, [query: query, message: "[Searching...]"])}
end
def handle_info({"onSearch", query}, socket) do
datas = Req.get!("https://qiita.com/api/v2/items?query=#{query}").body
{:noreply, assign(socket, [datas: datas, message: "[Complete!!]"])}
end
end
下記HTML側は、<form phx-change="onChange">
で検索キーワードが入力されるたびに handle_event("onChange", ~
が呼び出すされるようになっており、同時に Query: <%= @query %>
に検索キーワードを反映するようにしています
ここは、Vue.jsで言うところの v-on:change
に相当する訳ですが、Vue.jsやReactとの最大の違いは、フロントサイドの関数を呼ぶのでは無く、サーバサイドにあるElixirの関数が呼ばれる点です
<%
columns = ["id", "title", "created_at"]
%>
<p>
<%= if @message do %><%= @message %><% end %>
</p>
<form phx-change="onChange">
<input type="text" name="query" value={@query} placeholder="Please input keyword" class="container" /><br>
Query: <%= @query %><br>
</form>
<table class="container">
<tr>
<%= for column <- columns do %>
<th><%= column %></th>
<% end %>
</tr>
<%= for n <- @datas do %>
<tr>
<%= for column <- columns do %>
<td><%= n[column] %></td>
<% end %>
</tr>
<% end %>
</table>
HTMLからElixirサーバサイドの関数を呼び出し、HTMLに返す
handle_event("onChange", ~
に渡される引数である query
には、フロント側で入力された検索キーワードがサーバ側に引き渡されます
また、サーバ側の handle_event("onChange" ~
や handle_info({"onSearch" ~
で行われる message
の更新は、フロント上のメッセージ領域に反映されます
更に、handle_event("onChange", ~
からメッセージパッシングである send
で handle_info({"onSearch" ~
が呼び出され、handle_event("onChange", ~
はその直後に message: "[Searching...]"
でメッセージ領域を「[Searching...]」に更新し、その後、Qiita API呼出で検索された結果と、message: "[Complete!!]"
を返すことで、メッセージ領域を「[Complete!!]」に更新しつつ、検索結果がHTML側の <%= for n <- datas do %>
で始まる検索結果表示に反映されます
ルーティング追加
さて、SPAが実装できたので、後はルーティングを設定したら完成です
defmodule BasicWeb.Router do
use BasicWeb, :router
…
scope "/", BasicWeb do
pipe_through :browser
live "/", IndexLive
+ live "/qiita", QiitaLive
…
①の動作確認
ブラウザで「http://localhost:4000/qiita
」にアクセスすると、こんな感じのQiita検索SPA画面が表示されます
検索キーワードを入力すると、Query:
へと入力した文字列がリアルタイム反映され、上部には「[searching...]」と検索中であるメッセージが表示されます
その裏では、Qiitaへの検索が走っており、少しすると、メッセージが「[complete!!]」に変わり、画面下部に検索結果が反映されます
②「検索」ボタン押下時のみ検索(入力確認は即時反映)
業務系のWebアプリでは、入力都度に検索を走らせず、サブミットボタン押下時のみ検索を走らせるケースが多いと思いますが、LiveViewでその実装を叶えてみましょう
まずHTMLで、<from>
に phx-submit
を追加して、「検索」ボタンも追加することで、サブミット時の検索を可能にします
<%
columns = ["id", "title", "created_at"]
%>
<p>
<%= if @message do %><%= @message %><% end %>
</p>
-<form phx-change="onChange">
+<form phx-change="onChange" phx-submit="onSearch">
<input type="text" name="query" value={@query} placeholder="Please input keyword" class="container" /><br>
Query: <%= @query %><br>
+<input type="submit" value="検索" class="bg-blue-500 text-white py-2 px-4">
</form>
<table class="container">
<tr>
<%= for column <- columns do %>
<th><%= column %></th>
<% end %>
</tr>
<%= for n <- @datas do %>
<tr>
<%= for column <- columns do %>
<td><%= n[column] %></td>
<% end %>
</tr>
<% end %>
</table>
LiveViewモジュールは、handle_event("onChange", ~
での send
をしないことでQiita検索を止め、 handle_info({"onSearch", ~
を handle_event("onSearch", ~
に書き換えることで、サブミット時にQiita検索するように変更します
defmodule BasicWeb.QiitaLive do
use BasicWeb, :live_view
def mount(_params, _session, socket) do
{:ok, assign(socket, [query: "", datas: [], message: "[Init]"])}
end
def handle_event("onChange", %{"query" => query}, socket) do
- send(self(), {"onSearch", query})
- {:noreply, assign(socket, [query: query, message: "[Searching...]"])}
+ {:noreply, assign(socket, [query: query, message: ""])}
end
- def handle_info({"onSearch", query}, socket) do
+ def handle_event("onSearch", %{"query" => query}, socket) do
datas = Req.get!("https://qiita.com/api/v2/items?query=#{query}").body
- {:noreply, assign(socket, [datas: datas, message: "[Complete!!]"])}
+ {:noreply, assign(socket, [query: query, datas: datas, message: "[Complete!!]"])}
end
end
②の動作確認
検索キーワードを入力しても、Qiita検索は走らず、上部のメッセージは消え、入力したキーワードの Query:
へのリアルタイム反映だけが行われます
「検索」ボタンをクリックしたときだけ、Qiitaへの検索が走り、少しすると、メッセージが「[complete!!]」に変わり、画面下部に検索結果が反映されます
LiveViewの反応速度はスピーディで、もたつく感じは一切ありません(ReactやVue.jsでフロントを書いているフィーリングと、ほぼ変わらないでしょう)
【参考】本コラムの検証環境
本コラムは、以下環境で検証しています(恐らくUbuntu実機やMacでも動きます)
- Windows 11
- WSL2/Ubuntu 20.04+Elixir 1.15.5 (Erlang/OTP 25) ※最新版のインストール手順はコチラ
- Phoenix 1.7.7
- LiveView 0.19.0
- WSL2/Ubuntu 22.04+Docker Compose+Elixir 1.13.4 (Erlang/OTP 25)
- Phoenix 1.6.15
- LiveView 0.17.12
- WSL2/Ubuntu 20.04+Elixir 1.15.5 (Erlang/OTP 25) ※最新版のインストール手順はコチラ
- Windows 10
- 実機+Elixir 1.14.2 (Erlang/OTP 25)
- Phoenix 1.6.15
- LiveView 0.17.12
- WSL2/Ubuntu 20.04+Elixir 1.14.2 (Erlang/OTP 25) ※最新版のインストール手順はコチラ
- Phoenix 1.6.15
- LiveView 0.17.12
- Docker/Debian 11.6+Elixir 1.14.2 (Erlang/OTP 25)
- Phoenix 1.6.15
- LiveView 0.17.12
- 実機+Elixir 1.14.2 (Erlang/OTP 25)
終わり
LiveViewによって、Elixirサーバサイドのコードを書くだけで、フロントUIのリアルタイム操作/反映が叶いました
LiveViewが、SPAを作るための強力過ぎる土台だという感触が伝わったでしょうか?