LiveComponentとは
独自のコンポーネントの定義にはコアコンポーネント(CoreComponents)に記述したり、
関数コンポーネントなどを作成したりする。
LiveViewではLiveComponentを定義することができる。
このLiveComponentは、構成に必要なデータが更新されるごとにコンポーネントを更新してくれるメリットがある。
定義
LiveComponentは、次の2つの要点を考慮して記述を行う。
-
use ..., :live_component
の記述 -
render()
の定義 -
update()
の定義
use ..., :live_component
はLiveComponentを作成するための必要な記述である。
LiveComponentはマウントされた際に、次の順に関数を呼び出す。
mount()
update()
render()
mount()
はマウントされたときに実行される。
update()
はマウント後、または更新時に実行される。
render()
はupdate()
の実行後に実行される。
よって最低限必要になるのは、update()
とrender()
の定義ということになる。
LiveComponentを作成するための最低限のフォーマットは以下のようになる。
def ライブコンポーネント名 do
use 省略Web, :live_component
@impl true
def render(assigns) do
~H"""
ここにHTMLを記述。
"""
end
@impl true
def update(assigns, socket) do
{:ok,
socket
|> assign(assigns)
}
end
end
また、handle_event()
を追加してイベントハンドリングを行うこともできる。
注意
作成したLiveComponentに以下のボタンが存在するものとする。
<button phx-click="sample">Click</button>
このままでは上記の要素をクリックしたとき、イベントは
LiveComponentの呼び出し元に送信されてしまう。
イベントがLiveComponentに送信されるには、以下の記述を追加してイベントの送信先を指定する必要がある。
phx-target={@myself}
また、phx-target
を使用することで、別のLiveComponentにイベントを送信することもできる。
コンポーネントの呼び出し
LiveComponentを呼び出す際は、次のフォーマットに基づいて記述を行うと良い。
<.live_component
module={LiveComponentモジュールを指定}
id={idを指定}
# ここからはオプション
データ名=[データ]
/>
呼び出し元からデータを受け取って表示する場合、以下のような記述を行う。
<.live_component
...
name={@name} # socket.assigns.nameを渡している
list={@list} # socket.assigns.listを渡している
/>
ネスト
LiveComponentはタグをネストすることができる。
ネストしたタグは以下のように記述して表示することができる。
<%= render_slot(@inner_block) %>
使用例
使用例から、データの更新を自動で反映する具体的な動作を確認する。
Liveモジュールに、以下が記述されているものとする。
...
def mount(_params, _session, socket) do
{:noreyply,
socket
|> assign(:selected, nil)
}
end
def handle_event("button_click", %{"name" => name}, socket) do
{:noreply,
socket
|> assign(:selected, name)
}
end
heexテンプレートには、以下の要素が含まれているものとする。
<button phx-click="button_click" phx-value-name="A">Click A</button>
<button phx-click="button_click" phx-value-name="B">Click B</button>
LiveComponentには、以下が記述されているものとする。
def render(assigns) do
~H"""
<%= if ls_nil(@selected) do %>
<p>未選択</p>
<% else %>
<p><%= "#{@name}を選択中!" %>
<% end %>
"""
end
def update(assigns, socket) do
{:ok,
socket
|> assign(assigns)
}
end
ボタンをクリックすることで、マウント時に追加されたデータ(selected)が更新される。
この際、LiveComponentが更新され、データの更新を反映することができる。
LiveComponentの独立したライフサイクル
LiveComponent内でデータを更新しても、呼び出し元のLiveViewのデータには反映されない。
なぜなら、LiveComponentはLiveViewから独立したライフサイクルを持つからである。
例として、LiveViewのデータにselected: nil
というデータが存在するものとする。
テンプレートには、以下が記述されているものとする。
<%= if @selected do %>
<%= @selected %>
<% end %>
<.live_component
module={SampleButton}
selected={@selected}
/>
LiveComponentには以下が定義されているものとする。
defmodule SampleButton do
...
def render(assigns) do
~H"""
<button phx-click="select" phx-target={@myself} phx-value-name="apple">Click!</button>
"""
end
def update(assigns, socket) do
{:ok,
socket
|> assign(assigns)
}
end
def handle_event("select", %{"name" => name}, socket) do
{:noreply,
socket
|> assign(:selected, name)
}
end
このような場合、select
イベントが処理されてもLiveViewのデータに影響を与えないため、
selected
の内容がテンプレート内に展開されることはない。
LiveComponentからLiveViewのデータを更新したい場合、イベントの処理をLiveView側で行うとよい。
結論と感想
LiveComponentを使用することで、データの更新を反映するコンポーネントを記述
することができる。
ユーザの入力によって表示内容が変更されるコンポーネントは、
積極的にLiveComponentを使用すると良いと思った。
具体的にはメニュー(サブメニュー)、フォームの切り替えはLiveComponentを使用して実装したいと思った。
よくある問題
LiveComponentでflashメッセージを表示できない
この問題はLiveViewとLiveComponentは異なるsocketを持っているために発生する問題である。
LiveComponentのイベント処理でflashメッセージを表示させたい場合、LiveViewにメッセージを送信することで問題は解決する。
# LiveComponent
send self(), {:display_flash, {:info, "Flashメッセージ"}}
# LiveView
def handle_info({:display_flash, {:info, flash_message}}, socket) do
{:noreply,
socket
|> put_flash(:info, flash_message)
}
end
個人的には、特定のLiveComponentからのflashメッセージの表示であることをわかりやすくする場合、以下のように記述している。
ここではFileContentViewerというLiveComponentからflashメッセージを表示する例とする。
def handle_info({:display_flash_from_file_content_viewer, {:info, flash_message}}, socket) do
{:noreply,
socket
|> put_flash(:info, flash_message)
}
end