訳者から読者への注
すでにこの記事の内容は古くなっています。この注を書いている時点で LiveView のバージョンは 0.14.7 です。この記事が書かれたときは出たばかりのバージョン 0.0.x でしたので、当時に比較して内容が相当変わっています。あえてこの記事を読む場合は、歴史的な参考書として読んでください。
原文について
この記事は Dennis Beatty さんの書いた How to Create a Counter with Phoenix LiveView (Mar 19, 2019) を御本人の許可を得て日本語に翻訳した文書です。
大変簡潔なチュートリアルで、データベースも使ってませんので普段 RDB をお使いでない方でも簡単に LiveView を試せます。この文書だけでもお試しはできますが ご本人が実際にやって見せてる動画 もあるので、疑問に思ったらこれを観ながらやるのも良いかと思います。私は Elixir 1.8.1、Phoenix 1.4.2 の環境で、日本時間で 2019年3月20日に実行して滞りなくお試しできました。このチュートリアルは LiveView と同時に公開されたサンプルコードへの連続性もあるので、さらに多くのサンプルで学習することも可能です。
では以下から本文がはじまります。みなさん LiveView をお楽しみください!
編集履歴
- 2019.03.22 公開
- 2019.03.24 mix.exs に対して編集が必要な旨、npm の警告への対応、を追加
- 2019.03.25 参考文献として @piacere_ex さんのチュートリアルの2番目を1番目とまとめて最後においた
- 2019.03.26 @__hage さんの指摘で2箇所の間違いを修正
- 2019.03.31 @piacere_ex さんのチュートリアルの3番目を追加
- 2019.06.22 より検索で分かりやすいように、タイトルに「Elixir 入門者向け」を追加
- 2019.08.04 @nunulk さんより mix.exs に手を加える必要がなくなったとの情報を頂いて該当部を修正
- 2019.08.06 @nunulk さんより dev.exs と config.exs に対する編集が不要になったとの情報を頂いて該当部を修正
- 2020.10.17 記事の内容がすでに古くなっていることを訳注として先頭に記載。
はじめに
Elixir 関連のツイートを追っかけてる方なら、おそらく Chris McCord さんがこの金曜日にリリースしたPhoenix LiveView のことはご存知でしょう1。Chris さん(とこれに貢献した方たち)は徹底したドキュメントに加えて、数々の使用例 〜これは特に私が感銘を受けたものですが〜 も同時に出すという素晴らしい仕事をしました。ここに出てくる使用例の一つによく似たカウンタの例を通して、みなさんに LiveView の御案内をしてみます。全体のそれぞれのステップを可能な限り単純にして説明してみます。何か質問があったら私に Twitter で聞いてみてください。
プロジェクトの生成
まず最初に live_view_counter
という Phoenix を基にする Elixir プロジェクトを作りましょう。それには Phoenix generator を使います。今回はデータベースを使わないので Ecto なしで作りましょう。
mix phx.new live_view_counter --no-ecto
ここで [Y/n] で応答を求められます。依存関係を構築するのに Y
を入力してください。そしてプロジェクトフォルダの live_view_counter に cd して mix.exs
ファイルの deps
関数に以下の依存関係を追加してください。
{:phoenix_live_view, github: "phoenixframework/phoenix_live_view"}
その上で mix deps.get
コマンドを実行して最新の依存関係にしてください。
訳注再追加 (2019.08.04)
以下の修正は不要になっていたとの情報を @nunulk さんよりいただきました。
訳注追加 (2019.03.24)
このままやると以下のエラーが出るようになりました。
Dependencies have diverged:
* phoenix (Hex package)
the dependency phoenix in mix.exs is overriding a child dependency:
> In mix.exs:
{:phoenix, "~> 1.4.0", [env: :prod, repo: "hexpm", hex: "phoenix"]}
> In deps/phoenix_live_view/mix.exs:
{:phoenix, [env: :prod, git: "https://github.com/phoenixframework/phoenix.git", branch: "v1.4"]}
Ensure they match or specify one of the above in your deps and set "override: true"
** (Mix) Can't continue due to errors on dependencies
この場合 mix.exs
がこのようになってたら…
defp deps do
[
{:phoenix, "~> 1.4.0"},
...
]
以下のように override: true
を追加してください。
defp deps do
[
{:phoenix, "~> 1.4.0", override: true},
...
]
基本設定
ここで認証に使うソルトを入れたくなります。これは中間者攻撃 (man-in-the-middle attack) を防ぐためのセキュリティ手法です。あなた用のソルトを作るのに mix phx.gen.secret 32
が使えます。それを config に入れてください。なおこれはお試し用なんで config/config.exs
ファイルに直に書いたのを見せてますが、本番だったら見えるような場所に置いたりしません。2
config :live_view_counter, LiveViewCounterWeb.Endpoint,
...
live_view: [
signing_salt: "7HekGYwxATz33gM/rH9q2mV+uKJq5/Hu"
]
翻訳者注:以下の修正は不要になったとの指摘を @nunulk さんよりもらいました。2019.08.06
次の設定では、拡張子が .leex
であるようなレンダリングファイル用の LiveView テンプレートエンジンを使うことを Phoenix に伝えます。以下の2行を追加してください。
config :phoenix,
template_engines: [leex: Phoenix.LiveView.Engine]
では lib/live_view_counter_web/router.ex
ファイルに browser
パイプライン (pipeline) への LiveView フラッシュプラグ (flash plug) を追加してください。さらに LiveViewCounterWeb.LayoutView
を put_layout
として追加することで、デフォルトのレイアウトに新たに作るライブビュー (live view) をラップします。3
pipeline :browser do
...
plug :fetch_flash
plug Phoenix.LiveView.Flash
...
plug :put_layout, {LiveViewCounterWeb.LayoutView, :app}
end
さて lib/live_view_counter_web.ex
ファイルにいくつかのインポート (import) の記述をします。このファイルは Phoenix に隠されてる 魔術 の大きな部分です。このファイルはコントローラ (controller)、ビュー (view)、ルータ (router)、チャンネル (channel) を組み上げるためのエイリアス (alias) や関数をインポートします。役に立つ LiveView の関数をインポートするように書き換えてみます。
def view do
quote do
...
import Phoenix.LiveView, only: [live_render: 2, live_render: 3]
end
end
def router do
quote do
...
import Phoenix.LiveView.Router
end
end
次に lib/live_view_counter_web/endpoint.ex
ファイルに、LiveView の更新をクライアントに送るためのウェブソケット (websocket) の記述を入れましょう。
defmodule LiveViewCounterWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :live_view_counter
socket "/live", Phoenix.LiveView.Socket
...
end
そして LiveView に関する Javascript の依存性を assets/package.json
に追加します。これでクライアントがバックエンドの LiveView と話すようになります。
{
...
"dependencies": {
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view"
}
...
}
さあ cd assets && npm install
コマンドを実行して依存してる Javascript のパッケージをインストールしましょう4。これで設定のほとんどは完了です。assets/js/app.js
ファイルの一番最後に LiveView ソケットの接続を有効にする記述をしてください。
import LiveSocket from "phoenix_live_view"
let liveSocket = new LiveSocket("/live")
liveSocket.connect()
翻訳者注:以下の config/dev.exs
ファイルに対する編集は必要なくなったとの指摘を @nunulk さんよりいただきました。(2019.08.06)
そして config/dev.exs
ファイルに逐次リロード (live reload) 用の変更を施してください。これで LiveView テンプレートのリロードを連続的にできるようになります。
config :live_view_counter, LiveViewCounterWeb.Endpoint,
live_reload: [
patterns: [
...,
~r{lib/live_view_counter_web/live/.*(ex)$}
]
]
最後に(これは必須ではないですが)assets/css/app.css
ファイルに以下を追加してデフォルトの CSS スタイルを取り込んでください。
@import "../../deps/phoenix_live_view/assets/css/live_view.css";
さあ、セットアップが全部完了です!
全部の設定を確認したら mix phx.server
コマンドを実行してください。必要なコンパイルが実行されるので、ブラウザで http://localhost:4000 にアクセスしてみてください。Phoenix の画面が見えるはずです。コマンドを入力した端末画面には Replied phoenix:live_reload :ok
と出てくるでしょう。5
...
[info] Replied Phoenix.LiveView.Socket :ok
[info] Replied phoenix:live_reload :ok
Phoenix LiveView がもうちょっとばかり安定したら Phoenix generator に取り込まれるだろうと想像してます。そうなったらこういった決まりきった一連の設定を毎回やる必要はなくなるでしょう。さて、ここからが本当に楽しいところです。次へ行きましょう。
##訳注追加 (2019.03.24)
asset
フォルダ下で npm install
コマンドを実行すると、こんなエラーが出てました。
$ npm install
npm WARN assets No description
added 1 package from 1 contributor and audited 14925 packages in 6.902s
found 4 vulnerabilities (2 low, 2 moderate)
run `npm audit fix` to fix them, or `npm audit` for details
これ無視して進めてもこのチュートリアルはできるようです。しかしながら気持ち悪いですね。
もう少し掘ると npm audit
コマンドでどんな脆弱性があるのかわかります。指示では npm audit fix
コマンドを実施するように言ってきますが、これでは治らず npm audit fix --force
を実施する必要があります。
npm WARN using --force I sure hope you know what you are doing.
なんて言われますけど、脆弱性の表示はなくなります。
$ npm audit
=== npm audit security report ===
found 0 vulnerabilities
in 7695 scanned packages
カウンタの作成
カウンタを作るには lib/live_view_counter_web/router.ex
にあるデフォルトのルートを新しい live ルートに置き換えることからはじめます。
...
scope "/", LiveViewCounterWeb do
pipe_through :browser
live("/", CounterLive)
end
...
ルートの設定にあわせて新しいフォルダ live
を lib/live_view_counter_web
の下に作成して、さらにその下に counter_live.ex
ファイルを作ります。ファイル名の後ろに _live
をつけるのは LiveView の慣習で、同様にコントローラ (controller) にはファイル名の後ろに _controller
をつけます。LiveView のビュー (view) の生成を可能にするのに use Phoenix.LiveView
を入れることで必要な関数を持って来ます。
defmodule LiveViewCounterWeb.CounterLive do
use Phoenix.LiveView
end
このモジュールに render
関数を加えます。この関数は実際の DOM の要素を描画する live モジュール上の重要なポイントです。これで、現在の値を表示するカウンタの描画と、カウンタ値を増減させるためのボタンの描画をします。
def render(assigns) do
~L"""
<div>
<h1>The count is: <%= @val %></h1>
<button phx-click="dec">-</button>
<button phx-click="inc">+</button>
</div>
"""
end
複数行を挿入する先頭に ~L
という見慣れないシジルがあるでしょう。これは LiveView テンプレートを定義する新しい文法です。ここまでやると端末画面に例外が発生してるのが見られます。
** (ArgumentError) assign @val not available in eex template.
これは render
関数の中で @val
を使っているのに、値の設定をしていないからです。これを解決するのに LiveView の mount/2
関数を用います。この関数を counter_live.ex
ファイル内の render
関数記述の後ろに足してください。
def mount(_session, socket) do
{:ok, assign(socket, :val, 0)}
end
Phoenix のチャンネルに値を設定するのにも同じ文法を使います。mount/2
関数はビューの描画の際にコントローラから起動され、セッションの値と LiveView のソケットを受け取り、ビューの初期画面の描画に必要なデータを生成します。mount/2
関数が動作を完了したのちに render/1
関数が起動されます。ということで、基本的にはここでは @val
に 0 をセットしてからビューを描画します。ブラウザ画面を再描画させれば、正しい画面が見られます。
では、上で作ったボタンを見てみましょう。phx-click
属性がボタンについてるのが分かります。この属性は、クリックイベントを LiveView サーバに送るための紐付けを作成します。ではこれらのイベントを処理する関数を作りましょう。
def handle_event("inc", _, socket) do
{:noreply, update(socket, :val, &(&1 + 1))}
end
def handle_event("dec", _, socket) do
{:noreply, update(socket, :val, &(&1 - 1))}
end
ブラウザで +
や -
のボタンを使ってみてください。で、これがどのように動いているのか説明したいところですが、まずはとにかく
おめでとうございます!
あなたは LiveView のアプリケーションを作成して、そしてそれが動いてるってことです。🎉
handle_event
関数の第1引数に来る値の "inc"
と "dec"
は phx-click
属性から供給されます。今回は無視してる第2引数は phx-value
属性からの値が来るので、ボタンにこの属性を加えることで増減を1ではない別の値にするといったこともできます。6
#LiveEEx テンプレート
では実際に動くアプリケーションができたところで LiveEEx テンプレートを使ってリファクタリングしてみましょう。ちょうど今、私たちの作ったコードはビュー上にあります。lib/live_view_counter_web/templates/counter/index.html.leex
というファイルを作成することからはじめます。この新しい .leex
ファイルに、HTMLコードを render
関数からコピペします。
<div>
<h1 phx-click="boom">The count is: <%= @val %></h1>
<button phx-click="dec">-</button>
<button phx-click="inc">+</button>
</div>
そして lib/live_view_counter_web/views/counter_view.ex
ファイルに描画用の新しいビューを作成します。これで大変スッキリした記述になります。
defmodule LiveViewCounterWeb.CounterView do
use LiveViewCounterWeb, :view
end
そして新しいビューとテンプレートを使うように lib/live_view_counter_web/live/counter_live.ex
ファイルを変更します。
defmodule LiveViewCounterWeb.CounterLive do
use Phoenix.LiveView
alias LiveViewCounterWeb.CounterView
def render(assigns) do
CounterView.render("index.html", assigns)
end
...
end
ということで、テンプレートを使う描画ができて、ちょっとコードもキレイになりました! 🎉
次のステップ
Phoenix LiveView は、フロントエンドの Javascript コードを触る必要のないようなグッと来る手法を提供します。まだ初期段階ですので、製品に応用する前にはちょっと考えないとならないところもあります。とはいえ私はこのAPIが実に気に入ってます。多分、さらにデータベースを用いるようなチュートリアルも作ると思います。LiveView で何ができるのかもっと知りたいようでしたら LiveView の例題 をチェックするのをお勧めします。
もし LiveView を使ってなにか素敵なアプリケーションを構築したら是非教えてください。みなさんがどんな作品を作るのか楽しみです。この文章や動画についてコメントがあればフィードバックをください。Twitter では dnsbty で見つかりますし、動画に直接コメントしてくれても構いません。今後も動画を作り続ける予定ですので、ご興味があれば動画に「イイね」してくださるか、私のチャンネルを視聴してくれれば新作の御案内が YouTube から行くようになります。
ここまで読んでいただいてありがとうございました!
著作権 (Copyright notice)
The original document: Copyright © 2019 Dennis Beatty
The translation: Copyright © 2019 KIKUCHI Yutaka
その他の参考文献
以下は元の文書にはない情報です。
fukuoka.ex より
私の所属しているコミュニティ fukuoka.ex の代表をつとめる piacere さんによるチュートリアルもあります。併せてご覧ください。
- LiveViewを試す①: Phoenixでリアルタイム入力/通信/反映するSPAをAPI無で実現
- LiveViewを試す②: API無しQiita検索SPAをフォームsubmitスタイルに換装
- LiveViewを試す③: LiveView用のHTMLをテンプレートファイルに分離
akiba.ex より
akiba.ex @nunulk さんの pubsub を使った例がこちらです。
Phoenix LiveView と PubSub を組み合わせてリアルタイムにクライアントを更新する
-
訳注:ここでいう「この金曜日」とは 2019年3月15日(金)を指しています。 ↩
-
訳注:必ず自分で生成して他人にはみられないようにして置いてください。 ↩
-
訳注:LiveView に view という概念があって、それが動的にどんどん描画されるライブな状態を合わせて live view と書いているようなんですが、適当な訳語が思いつかないので「ライブビュー」と訳します。 ↩
-
訳注 (2019.03.24) 私の環境では脆弱性の warning が出て「こうせよ」と指示が出ました。これ従わなくても従ってもチュートリアルは続行できます。 ↩
-
訳注:ここではまだカウンタについては何も記述していないので Phoenix の画面が見えるだけです。 ↩
-
訳注:テンプレートに
<button phx-click="dec" phx-value=2>-</button>
と、ライブ関数の第2引数をv
として{:noreply, update(socket, :val, &(&1 - String.to_integer(v)))}
と記述すると「+」ボタンを押すと2ずつ増加するようになります。 ↩