Chapter 08

エントリポイント

黒田努
黒田努
2024.12.06に更新

この章で学ぶこと

  • 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 つの引数を取ります。

  1. パラメータ
  2. セッション
  3. LiveView ソケット

パラメータに関しては第 5 章で説明しました。セッションに関しては(将来刊行される)別の巻で説明する予定です。

では、RobotLive モジュールにエントリポイントを加えましょう。robot_live.ex を次のように書き換えてください:

lib/jaunty_greeter_web/live/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 ソケットの初期化です。

assings マップと関数 assign/3

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 を次のように書き換えてください:

lib/jaunty_greeter_web/live/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