Phoenix1.7 LiveViewStreamについてゆるく学ぶ

こんにちは！
プログラミング未経験文系出身、Elixirの国に迷い込んだ？！見習いアルケミストのaliceと申します。
今回は、2023/7/1開催「[piyopiyo.ex #17：Phoenix1.7のソースコードを眺めてみよう」](https://piyopiyoex.connpass.com/event/286142/)で学んだ「LiveViewStreamの機能」についてまとめます。


# 目的
Phoenix1.7で実装されたLiveViewStreamの機能についてゆるく把握します。

## 実行環境
Windows 11 ＋ WSL2 ＋ Ubuntu 22.04
Elixir v1.14.3
Phoenix v1.7.2


# 概要
・LiveViewStream
　└HTMLの部分レンダリング
　　└SPAとしての機能がより洗練された

※Phoenix.LiveViewはSPAを開発できるPhoenixフレームワークの拡張機能です。

# SPAとは？
そもそも、SPAとは何でしょう？
一般的な3層アーキテクチャに基づくWebアプリケーション[^0]は、ブラウザからのHTTPリクエストはユーザーの操作に基づく同期処理であり、操作の度にページ全体の再レンダリングを行います。

一方、SPAはWebブラウザがWebサーバに対して静的ファイル (html, css, javascript) をリクエストしダウンロードします。その後はダウンロードしたjavascript が REST APIサーバに対してHTTPリクエストを投げます。[^1]

以後は非同期処理でこのようにHTMLをレンダリングしていきます。[^2]
1. ユーザーがアクション（クリックやフォームの送信など）を行う
1. そのアクションによって起こされる**必要な部分のデータだけ**をサーバに要求
1. 返ってきたデータをJavaScriptで処理
1. HTMLに反映

つまり、SPAの場合、**必要な最小限のデータだけ**をブラウザとサーバーの間でやりとりをします。
これにより、ページ遷移を速めることができるというわけです。

# これまでのLiveViewはどうだったの？
@piacerex さん曰く、
> 「そもそもフロントとサーバで上り下りするDOM自体に無駄があったという感じですね
更新の必要が無いDOMまで上り下りしてたのが、今までのPhoenix／LiveViewで、それを改善したのがstreamsです」

とのこと。

# はて？
どういうことが起こっていたのでしょうか？
こちらの記事にて実践してくださった方がおられます。

https://blog.emattsan.org/entry/2023/02/27/200000

どうやら今まではLiveViewによるページの差分レンダリングにおいて、特に削除の機能について機能的な不足があったようです。

https://blog.emattsan.org/entry/2020/12/31/103714#temporary_assigns-%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%97%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AE%E5%89%8A%E9%99%A4%E5%95%8F%E9%A1%8C

> ただし可能なのは、新規の DOM の追加と既存の DOM の更新だけで、削除はできません。 削除するには、該当する DOM を削除した全体をサーバからクライアントに送り、再描画する必要があります。 要素の数が少なければそのような方法も使えますが、数が多くなると送信のコストも再描画のコストも無視できなくなります。

これを解消したのが、Phoenix1.7にて追加されたLiveViewStreamの機能のようです。


# どう変わったのか？
1.テーブル内の「delete」ボタンがクリックされる = 要素の削除イベントをトリガーする。
```elixir:index.html.heex
<.table
  id="user"
  rows={@streams.user_collection}
  row_click={fn {_id, user} -> JS.navigate(~p"/user/#{user}") end}
>

#------中略------

 <:action :let={{id, user}}>
    <.link
      phx-click={JS.push("delete", value: %{id: user.id}) |> hide("##{id}")}
      data-confirm="Are you sure?"
    >
      Delete
    </.link>
  </:action>
```

2.ハンドラを経由してDBに要素の削除を要求する。
```elixir:index.ex
　@impl true
  def handle_event("delete", %{"id" => id}, socket) do
    user = Users.get_user!(id)
    {:ok, _} = Users.delete_user(user) #ここまで

    {:noreply, stream_delete(socket, :user_collection, user)}
  end
```

3.削除されるべきデータを特定し、要素の削除を実行する。ハンドラに結果を渡す。
```elixir:users.ex
def get_user!(id), do: Repo.get!(User, id)

#------中略------

def delete_user(%User{} = user) do
    Repo.delete(user)
  end
```

4.ハンドラは結果を受け取り、要素が削除されたことをstream上に反映させる（stream_delete/3）
**※削除後のテーブル全体を再取得、再レンダリングする必要がなくなった**
```elixir:index.ex
  @impl true
  def handle_event("delete", %{"id" => id}, socket) do
    user = Users.get_user!(id)
    {:ok, _} = Users.delete_user(user)

    {:noreply, stream_delete(socket, :user_collection, user)} #この部分
  end
```

5.要素が削除されたテーブルをブラウザ上にリアルタイムに反映させる。


## 不明点
stream_deleteとDOM_IDの関連性、そもそもDOMって何？が分かっていないので、ふわっとした理解しかできていません。
もう少し深掘りできたら追記していこうと思います。


# ～Elixirの国のご案内～
↓Elixirって何ぞや？と思ったらこちらもどぞ。Elixirは先端のアレコレをだいたい全部できちゃいます:laughing::sparkles::sparkles:

https://qiita.com/piacerex/items/09876caa1e17169ec5e1

↓ゼロからElixirを始めるなら「エリクサーチ」がおすすめ！私もエンジニア未経験から学習中です。

https://elixir-lang.info/topics/entry_column


↓[We Are The Alchemists, my friends!:bouquet:](https://qiita.com/torifukukaiou/items/6bcd5bbb80df9e594e9e "Awesome-san's Awesome article")[^10]
Elixirコミュニティは本当に優しくて温かい人たちばかり！
私が挫折せずにいられるのもこの恵まれた環境のおかげです。
まずは気軽にコミュニティを訪れてみてください。[^11]


https://speakerdeck.com/elijo/elixirkomiyunitei-falsebu-kifang-guo-nei-onrainbian


[^0]: プレゼンテーション層としてのWebブラウザ、Webサーバ、アプリケーション層としてのAPサーバー、データ層としてのDBサーバーの3層構造

[^1]: https://milestone-of-se.nesuke.com/sv-basic/web-tech-basic/single-page-application/#:~:text=%E3%81%AE%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88%E3%83%BB%E3%83%87%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88-,SPA%20%E3%81%A8%E3%81%AF,Portal%20%E7%AD%89%E3%81%8C%E6%9C%89%E5%90%8D%E3%81%A7%E3%81%99%E3%80%82

[^2]: https://digitalidentity.co.jp/blog/creative/about-single-page-application.html#i

[^10]: @torifukukaiouさんのAwesomeな名言をお借りしました。Elixirコミュニティを一言で表すと、これに尽きます。


[^11]: @kn339264さんの素敵なスライドをお借りしました。Elixirコミュニティはいろんな形で活動中！