はじめに
今回は関数コンポーネントをはじめ、細かな部分について実装中に不思議に思ったことを中心にまとめました。
初めてのインプットでは「そういういものなんだ!」と素通りしていたところも二度三度やっていると「なぜ?どうして?」と疑問に思った部分です。
テーマはバラバラですが、どなたかの理解の一助になると嬉しいです。
関数コンポーネントとは
ElixirのPhoenixフレームワークでテンプレートの一部を再利用可能な小さな部品として定義するための機能。
以下2種類ある
-
core_components.ex
に定義されているコンポーネント - LiveViewが用意しているコンポーネント
参考:LiveViewが用意しているコンポーネントについて
hexdocs phoenix_liveView Phoenix.Component
上記以外については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のドキュメントを見るとTypes
のt/0
に以下のように書いています。
@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}
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
ジェネレーター生成
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]
@spec map(t(), (element() -> any())) :: list()
おわりに
点と点と繋いでいけるように精進します!