この章で学ぶこと
- LiveView プロセスとは
- LiveView ソケットとは
- エントリポイントとは
- assigns マップとは
- assings マップを初期化する方法
- assings マップの値を HEEx テンプレートに埋め込む方法
LiveView プロセス
プロセス
すべての Elixir コードは仮想マシン BEAM の上で動きます。BEAM の上で動いている Elixir コードをプロセス(process)と呼びます。
各プロセスは固有の状態とメールボックスを持っています。プロセス同士はメッセージを送り合うことができます。メッセージはプロセスのメールボックスに入ります。プロセスはメールボックスに届いたメッセージを読み取り、その内容に応じて自身の状態を更新します。
軽量プロセス
OS の「プロセス」と区別するため、仮想マシン BEAM におけるプロセスはしばしば軽量プロセス(lightweight process)と呼ばれます。
OS のプロセスとは実行中のプログラムです。BEAM もそのひとつです。軽量プロセスは BEAM の中で動きます。
BEAM 上のプロセスが「軽量」と呼ばれる理由は次の 2 つです。
- メモリの消費量が少ない。
- 生成時における CPU への負荷が小さい。
軽量プロセスは「スレッド(thread)」とは別物です。スレッドは OS レベルの概念であり、BEAM に限らずすべてのプロセスの中に存在ます。また、プロセス内のスレッド群がアドレス空間を共有するのに対し、軽量プロセスは互いに独立しています。
LiveView プロセスの生と死
ブラウザと Phoenix サーバーの間で WebSocket 通信が成立すると Phoenix サーバー側で LiveView プロセス が誕生し、 WebSocket 通信が途絶えると死にます。生まれてから死ぬまでの間、LiveView プロセスのメールボックスにはブラウザからのメッセージが次々と届きます。
LiveView プロセスとブラウザは一対一の関係にあります。1 万個のブラウザが Phoenix LiveView ベースのシングルページアプリケーションを開いているとすれば、仮想マシン BEAM の中に 1 万個の LiveView プロセスが存在し、メッセージを待ち受けていることになります。
LiveView ソケット
LiveView プロセスの状態を LiveView ソケットと呼びます。日常用語の「ソケット」は電球を取り付ける電気器具を意味しますが、そのイメージに引きずられないでください。
LiveView ソケットの実体は、Phoenix.LiveView.Socket
構造体です。生まれてから死ぬまでの間、LiveView プロセスはこの構造体を保持し続けます。
LiveView モジュールと LiveView プロセスの関係
前章で RobotLive
という名前の LiveView モジュールを定義しました。これと LiveView プロセスはどのような関係にあるのでしょうか。
LiveView モジュールは「箱」のようなものです。その中には複数の関数が入っています。LiveView プロセスは誕生時に特定の「箱」と結び付けられます。
LiveView プロセスのメールボックスにメッセージが届くと、その内容に応じて「箱」の中にある特定の関数が選び出され、その関数にメッセージと自分自身の状態(LiveView ソケット)を引数として渡します。その関数は、受け取った LiveView ソケットを更新して返します。これが LiveView プロセスの新たな状態となります。
エントリポイント
mount/3
関数 LiveView モジュールの関数 mount/3
はエントリポイント(entry-point)と呼ばれます。シングルページアプリケーションが初期化される際にこの関数が呼び出されます。
エントリポイント mount/3
は 3 つの引数を取ります。
- パラメータ
- セッション
- LiveView ソケット
パラメータに関しては第 5 章で説明しました。セッションに関しては(将来刊行される)別の巻で説明する予定です。
では、RobotLive
モジュールにエントリポイントを加えましょう。robot_live.ex
を次のように書き換えてください:
defmodule JauntyGreeterWeb.RobotLive do
use JauntyGreeterWeb, :live_view
+ def mount(_params, _session, socket) do
+ socket =
+ socket
+ |> assign(:counter, 0)
+ |> assign(:name, "world")
+
+ {:ok, socket}
+ end
+ end
第 3 引数 socket
には空の LiveView ソケット(LiveView プロセスの状態を保持する構造体)が渡されます。エントリポイントの主な役割は、LiveView ソケットの初期化です。
assign/3
assings マップと関数 LiveView ソケットには assigns
というマップ型のフィールドがあります。Elixir の公式ドキュメントではこれを「socket.assigns」あるいは「assigns」と呼んでいますが、本書ではassigns マップと呼びます。
assigns マップにセットされた値は、LiveView プロセスが HTML 文書をレンダリングする際に参照されます。
関数 assign/3 は、assigns マップに値をセットするために利用されます。
例えば次のように書くと、"Red"
という値を持つ :color
というキーが assigns マップに追加されます。
socket = assign(socket, :color, "Red")
さて、robot_live.ex
の 5-8 行をご覧ください:
socket =
socket
|> assign(:counter, 0)
|> assign(:name, "world")
Elixir の特色であるパイプ演算子(|>
)が使われています。パイプ演算子を使わずに書き直すと、次のようになります。
socket = assign(socket, :counter, 0)
socket = assign(socket, :name, "world")
つまり、robot_live.ex
の 5-8 行では、LiveView ソケットの assigns
マップに対して 0 および "world"
という 2 つの値をセットしているというわけです。
関数 Map.put/3 は使わないで!
LiveView ソケットの assigns
フィールドにセットされているのはマップです。通常、マップに対して値をセットする際には、関数 Map.put/3
を使います。assign(socket, :color, "Red")
と書く代わりに、(冗長になりますが)次のようにも書けそうです:
assigns = Map.put(socket.assigns, :color, "Red")
%{socket | assigns: assigns}
しかし、これはうまく行きません。なぜならソケットの assigns
マップは :__changed__
という特殊なキーを持っていて、これを正しく変化させないと HTML 文書が正しく生成されないからです。
したがって、もし assign/3
関数を使わないのであれば、こんな風に書く必要があります:
changed = Map.put(socket.assigns.__changed__, :color, true)
assigns =
socket.assigns
|> Map.put(:color, "Red")
|> Map.put(:__changed__, changed)
%{socket | assigns: assigns}
とても面倒ですよね。だから、LiveView ソケットの assigns
マップに値をセットする際には、関数 Map.put/3
ではなく関数 assign/3
を利用してください。
assings マップの値をブラウザ上に表示する
エントリポイントの中で LiveView ソケットの assigns
マップにセットされた値は @
マクロにより、HEEx テンプレートの中に埋め込むことができます。
robot_live.html.heex
を次のように書き換えてください:
- <div>Robot</div>
+ <div><span class="font-bold">counter:</span> {@counter}</div>
+ <div><span class="font-bold">name:</span> {@name}</div>
すると、ブラウザの画面は次のような表示に変化します。
コミット履歴
- 6287200 [ch07] RobotLive: assign and embed values