LoginSignup
137
71

ExcelからElixir入門⑨:ElixirサーバサイドのみでReactと同じSPA/リアルタイムUIが作れる「LiveView」(APIとJavaScriptは書いていない)

Last updated at Posted at 2019-03-19

【本コラムは、5分で読めて、10分くらいでお試しいただけます】
piacereです、ご覧いただいてありがとございます :bow:

今回は、「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をお楽しみください :wink:
chrome-capture-2023-0-13 (1).gif

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
mix.exsを編集
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回にて解説済みなので、忘れてしまった方は各リンクを見返してください

lib/basic_web/live/qiita_live.ex
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の関数が呼ばれる点です

lib/basic_web/live/qiita_live.html.heex
<%
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", ~ からメッセージパッシングである sendhandle_info({"onSearch" ~ が呼び出され、handle_event("onChange", ~ はその直後に message: "[Searching...]" でメッセージ領域を「[Searching...]」に更新し、その後、Qiita API呼出で検索された結果と、message: "[Complete!!]" を返すことで、メッセージ領域を「[Complete!!]」に更新しつつ、検索結果がHTML側の <%= for n <- datas do %> で始まる検索結果表示に反映されます

ルーティング追加

さて、SPAが実装できたので、後はルーティングを設定したら完成です

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

  scope "/", BasicWeb do
    pipe_through :browser

    live "/", IndexLive
+   live "/qiita", QiitaLive

①の動作確認

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

検索キーワードを入力すると、Query: へと入力した文字列がリアルタイム反映され、上部には「[searching...]」と検索中であるメッセージが表示されます
image.png

その裏では、Qiitaへの検索が走っており、少しすると、メッセージが「[complete!!]」に変わり、画面下部に検索結果が反映されます
image.png

②「検索」ボタン押下時のみ検索(入力確認は即時反映)

業務系のWebアプリでは、入力都度に検索を走らせず、サブミットボタン押下時のみ検索を走らせるケースが多いと思いますが、LiveViewでその実装を叶えてみましょう

まずHTMLで、<from>phx-submit を追加して、「検索」ボタンも追加することで、サブミット時の検索を可能にします

lib/basic_web/live/qiita_live.html.heex
<%
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検索するように変更します

lib/basic_web/live/qiita_live.ex
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: へのリアルタイム反映だけが行われます
image.png

「検索」ボタンをクリックしたときだけ、Qiitaへの検索が走り、少しすると、メッセージが「[complete!!]」に変わり、画面下部に検索結果が反映されます
image.png

LiveViewの反応速度はスピーディで、もたつく感じは一切ありません(ReactやVue.jsでフロントを書いているフィーリングと、ほぼ変わらないでしょう)

【参考】本コラムの検証環境

本コラムは、以下環境で検証しています(恐らくUbuntu実機やMacでも動きます)

  • Windows 11
  • 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

終わり

LiveViewによって、Elixirサーバサイドのコードを書くだけで、フロントUIのリアルタイム操作/反映が叶いました

LiveViewが、SPAを作るための強力過ぎる土台だという感触が伝わったでしょうか?

次回は、今回作成した LiveView SPAアプリをGigalixirにリリース します

137
71
5

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
  3. You can use dark theme
What you can do with signing up
137
71