3
1

Elixir経験半年のヒヨッ子が、Phonixの関数コンポーネントとその他の小さな疑問をまとめてみた

Last updated at Posted at 2024-06-22

はじめに

今回は関数コンポーネントをはじめ、細かな部分について実装中に不思議に思ったことを中心にまとめました。
初めてのインプットでは「そういういものなんだ!」と素通りしていたところも二度三度やっていると「なぜ?どうして?」と疑問に思った部分です。
テーマはバラバラですが、どなたかの理解の一助になると嬉しいです。

関数コンポーネントとは

ElixirのPhoenixフレームワークでテンプレートの一部を再利用可能な小さな部品として定義するための機能。
以下2種類ある

  • core_components.exに定義されているコンポーネント
  • LiveViewが用意しているコンポーネント

参考:LiveViewが用意しているコンポーネントについて

hexdocs phoenix_liveView Phoenix.Component

スクリーンショット 2024-06-19 11.19.29.png

上記以外についてはcore_components.exに定義されています。
core_components.exに自分で定義することもできます。
ざっくりとlink/1以外はcore_components.exに定義されている関数コンポーネントという理解で良いです。

例えば以下のようにcore_components.exに定義されているbackという関数コンポーネントは内部でLiveViewが用意している関数コンポーネントを呼び出しています。


  @doc """
  Renders a back navigation link.

  ## Examples

      <.back navigate={~p"/posts"}>Back to posts</.back>
  """
  attr :navigate, :any, required: true
  slot :inner_block, required: true

  def back(assigns) do
    ~H"""
    <div class="mt-16">
      <.link
        navigate={@navigate}
        class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
      >
        <.icon name="hero-arrow-left-solid" class="h-3 w-3" />
        <%= render_slot(@inner_block) %>
      </.link>
    </div>
    """
  end

.linkコンポーネントの属性について

.linkコンポーネントは以下のような2つの書き方ができます。

    <.link navigate={~p"/tasks/#{task}/edit"}>編集する</.link>
    <.link href={~p"/tasks/#{task}"} method="delete" data-confirm="本当に削除しても良いですか?">削除する</.link>

navigate属性とhref属性の違い

navigate属性は基本的にLiveViewページに移動するときに使用する。(新しいプロセスを開始してマウントする時に使う)。LiveViewを使っていない場合はhrefで問題ないです。

.simple_formコンポーネントを解読する

<.simple_form :let={f} for={@changeset} action={@action}>
  <.error :if={@changeset.action}>
    Oops, something went wrong! Please check the errors below.
  </.error>
  <.input
    field={f[:category_id]}
    type="select"
    label="category"
    options={@categories}

  />
  <.input field={f[:name]} type="text" label="Name" />
  <.input field={f[:money]} type="number" label="Money" />
  <.input field={f[:date]} type="date" label="Date" />
  <:actions>
    <.button>Save Purchase</.button>
  </:actions>
</.simple_form>

@changeset.actionとは?

Ecto.changesetのドキュメントを見るとTypest/0に以下のように書いています。

Ecto.changeset Types

@type t(data_type) :: %Ecto.Changeset{
  action: action(),
  changes: %{optional(atom()) => term()},
  constraints: [constraint()],
  data: data_type,
  empty_values: term(),
  errors: [{atom(), error()}],
  filters: %{optional(atom()) => term()},
  params: %{optional(String.t()) => term()} | nil,
  prepare: [(t() -> t())],
  repo: atom() | nil,
  repo_opts: Keyword.t(),
  required: [atom()],
  types:
    nil
    | %{required(atom()) => Ecto.Type.t() | {:assoc, term()} | {:embed, term()}},
  valid?: boolean(),
  validations: [{atom(), term()}]
}

actionの中の定義は以下
Ecto.changeset Action

@type action() :: nil | :insert | :update | :delete | :replace | :ignore | atom()

どういう操作をしたかがactionのキーに入ってきます。

fとは何か?

<pre><%= inspect(f) %></pre>を入れて確認したところ以下のような値が入っていました。


%Phoenix.HTML.Form{source: #Ecto.Changeset<action: nil, changes: %{}, errors: [name: {"can't be blank", [validation: :required]}, money: {"can't be blank", [validation: :required]}, date: {"can't be blank", [validation: :required]}], data: #HouseholdAccountBookApp.Purchases.Purchase<>, valid?: false>, impl: Phoenix.HTML.FormData.Ecto.Changeset, id: "purchase", name: "purchase", data: %HouseholdAccountBookApp.Purchases.Purchase{__meta__: #Ecto.Schema.Metadata<:built, "purchases">, id: nil, name: nil, date: nil, money: nil, category_id: nil, category: #Ecto.Association.NotLoaded<association :category is not loaded>, inserted_at: nil, updated_at: nil}, action: nil, hidden: [], params: %{}, errors: [], options: [method: "post", multipart: false], index: nil}

examples-inside-liveview

Inside LiveViews, this function component is typically called with as for={@form}, where @form is the result of the to_form/1 function. to_form/1 expects either a map or an Ecto.Changeset as the source of data and normalizes it into Phoenix.HTML.Form structure.

Using the for attribute
The for attribute can also be a map or an Ecto.Changeset. In such cases, a form will be created on the fly, and you can capture it using :let:

関数において!つけるつけないの区別

例外が出る可能性がある場合は!をつけます。
例外を出さない関数は!をつけないです。

例外出る出ないの区別とは?

例外とは引数に違う値を渡して例外が出るなど予想がつく範囲での例外を指します。

def list_tasks(), do: Repo.all(Task)

def get_task!(id), do: Repo.get!(Task, id)

例えば上記のようにTaskスキーマから全権データを取得する場合は例外は発生しない、Taskスキーマから該当idを取得するという場合は該当idがないとエラーになるので例外が発生するとか考えます。

redirectについて

本来%Task{}構造体はid、date,titleを持っていますが、redirectで指定した時に#{task}と指定すると#{task.id}に変換されます。

  def update(conn, %{"id" => id, "task" => params}) do
    task = Tasks.get_task!(id)
    case Tasks.update_task(task, params) do
      {:ok, task} ->
      conn
      |> put_flash(:info, "update task.")
      |> redirect(to: ~p"/tasks/#{task}")
    {:error, cs} ->
      render(conn, :edit, task: task, changeset: cs)
    end
  end

phoenix routerがルーティングで:idと指定しているのでその値を持ってきている

patch "/tasks/:id", TaskController, :update

ジェネレーター生成

phoenix

LiveViewのジェネレーターmix phx.gen.live

PhoenixとLiveViewの違い

Phoenix Framework

  • Webフレームワーク: PhoenixはElixir言語で書かれたフルスタックのWebフレームワークです。高いパフォーマンスとスケーラビリティを提供し、リアルタイム機能のサポートが優れている
  • MVCアーキテクチャ: PhoenixはModel-View-Controller (MVC) アーキテクチャに従っており、伝統的なサーバーレンダリングやクライアントサイドのJavaScriptを利用したインタラクティブなWebアプリケーションを構築可能

Phoenix LiveView

  • リアルタイム更新: LiveViewはPhoenixフレームワークの一部で、リアルタイムな双方向のユーザーインターフェースを構築するためのツールです。JavaScriptを使わずに、サーバーサイドでレンダリングされたHTMLをWebSocketを通じてリアルタイムに更新可能
  • コンポーネントベース: LiveViewはコンポーネントベースのアプローチを採用しており、UIコンポーネントを効率的に管理・再利用可能
    シームレスなインタラクション: フロントエンドとバックエンドの間でシームレスなインタラクションを可能にし、ユーザー体験を向上

cookieとセッションの操作

  • cookie: LiveView内でcookieを操作することはできます。通常のElixirやPhoenixの方法を使用してcookieの読み書きを行うことが可能
  • セッション: LiveViewはセッション情報を読み取ることができますが、直接セッションを更新することはできません。セッションを更新する必要がある場合は、従来のPhoenixコントローラやPlugを使用する必要があります。セッションは、LiveViewプロセスが開始される前に設定されるため、動的に変更することはできない

モジュール関数と匿名関数

匿名関数

add = fn a, b -> a + b end

add変数にバインドされた匿名関数を呼び出すためには、.()構文を使用します。

x = add.(1, 1)

モジュール関数

defmodule Hello do
  def call(name) do
    "Hello, " <> name
  end
end

モジュールHelloのcall/1関数を呼び出すためには、モジュール名Helloを指定し、ドット演算子.を使用して関数を呼び出します。

message = Hello.call("TheWaggle")

Map型もEnum.mapを使うとリスト型となる

意識してドキュメントを見ないと見落とします。Enum.mapの戻り値は常にlistです

iex> Enum.map(%{a: 1, b: 2}, fn x -> x end)
[a: 1, b: 2]

参考 hexdoc Enum.map

@spec map(t(), (element() -> any())) :: list()

おわりに

点と点と繋いでいけるように精進します!

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