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


# はじめに

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

# 関数コンポーネントとは

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

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

## 参考：LiveViewが用意しているコンポーネントについて
[hexdocs phoenix_liveView Phoenix.Component](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html)

![スクリーンショット 2024-06-19 11.19.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3743559/10c567cd-5598-0303-c44c-af918ceed12a.png)

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

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

```elixir

  @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つの書き方ができます。

```elixir
    <.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`コンポーネントを解読する

```elixir
<.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`に以下のように書いています。

[Ecto.changeset Types](https://hexdocs.pm/ecto/Ecto.Changeset.html#t:t/0)

```elixir
@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](https://hexdocs.pm/ecto/Ecto.Changeset.html#t:action/0)

```elixir
@type action() :: nil | :insert | :update | :delete | :replace | :ignore | atom()
```
どういう操作をしたかがactionのキーに入ってきます。

### `f`とは何か？

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

```elixir

%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](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#form/1-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:

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

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

## 例外出る出ないの区別とは？

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

```elixir  
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}`に変換されます。

```elixir
  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`と指定しているのでその値を持ってきている

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

# ジェネレーター生成

[phoenix](https://hexdocs.pm/phoenix/Phoenix.html)

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プロセスが開始される前に設定されるため、動的に変更することはできない

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


## 匿名関数
```elixir
add = fn a, b -> a + b end
```
add変数にバインドされた匿名関数を呼び出すためには、.()構文を使用します。
```elixir
x = add.(1, 1)
```

## モジュール関数
```elixir
defmodule Hello do
  def call(name) do
    "Hello, " <> name
  end
end
```
モジュールHelloのcall/1関数を呼び出すためには、モジュール名Helloを指定し、ドット演算子.を使用して関数を呼び出します。
```elixir
message = Hello.call("TheWaggle")
```

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

意識してドキュメントを見ないと見落とします。`Enum.map`の戻り値は常に`list`です
```elixir
iex> Enum.map(%{a: 1, b: 2}, fn x -> x end)
[a: 1, b: 2]
```

[参考 hexdoc Enum.map](https://hexdocs.pm/elixir/1.12/Enum.html#map/2)
> @spec map(t(), (element() -> any())) :: list()

# おわりに

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