15
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ElixirAdvent Calendar 2023

Day 1

LiveComponentについて

Last updated at Posted at 2023-11-02

LiveComponentとは

独自のコンポーネントの定義にはコアコンポーネント(CoreComponents)に記述したり、
関数コンポーネントなどを作成したりする。

LiveViewではLiveComponentを定義することができる。

このLiveComponentは、構成に必要なデータが更新されるごとにコンポーネントを更新してくれるメリットがある。

定義

LiveComponentは、次の2つの要点を考慮して記述を行う。

  • use ..., :live_componentの記述
  • render()の定義
  • update()の定義

use ..., :live_componentはLiveComponentを作成するための必要な記述である。

LiveComponentはマウントされた際に、次の順に関数を呼び出す。

  1. mount()
  2. update()
  3. 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
15
3
0

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
15
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?