今回はViewの話です。
Rendering Template
今までJavaをよく業務で使ってたが、まず大きく違うのがViewの存在。
Springなんかだと、Controller -> thymeleafなどのテンプレート の2つから成り立つ。
phoenixの場合、Controller -> View + template(.eex) の3要素になる。
templateはよくある、htmlなどを動的に生成する時に使うやつだ。eexという形式で書く。
よくあるように、変数や関数の実行結果を埋め込むことが出来る。
<div class="jumbotron">
<h2><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h2>
<p class="lead"><%=message()%></p>
<p class="lead">A productive web framework that<br />does not compromise speed and maintainability.</p>
<p><a href="<%= Routes.page_path(@conn, :index) %>">Link back to this page </a></p>
</div>
(eexはハイライト効かないのか・・・)
Viewはexコードだ。
ここで定義した関数は、そのままtemplate上で利用可能となる。
defmodule HelloWeb.PageView do
use HelloWeb, :view
alias HelloWeb.Router.Helpers ,as: Routes
def message do
"Hello from the view"
end
end
これが、良い。
Webの処理って、リクエストを受けて処理する部分 と 画面表示用の処理をする部分 があると思うのだが
この表示用の方の立ち位置が曖昧だったと思うのだ。
表示用の処理だから表示側に寄せたいが、テンプレートで複雑なロジックは可読性を下げるし、そもそもロジックを書くのに向いていない。
昨今のSPAのような形も一つの解だと思うが、
サーバ側の処理を明確に分けることが出来るのは、とても嬉しい。
あと以下の様な特徴がある。
- Controllerとの対応は、命名で決まってる
- PageControllerならば、PageViewとtemplates/page/ ディレクトリ
- templateは、phoenixによってプリコンパイルされてViewの関数として組み込まれる
- だからView上の関数はそのまま使えるし、aliasなんかもViewに追加すればtemplateで使える
- render("index.html", _assign) のような形
- 先にView上で定義しておけば、index.html.eexレンダリングの代わりに実行される
- サーバ起動時点でメモリに乗ってる。リクエスト毎にファイルを読んでるわけではない
- 代わりに、テンプレート変更時でも再コンパイルが必要
More About Views
- .eexで変数の表示は "@"を使う
- htmlエスケープも勝手にされる
- layoutっていう外枠表示してるものも同じ仕組み(View + template)
- templates/layout/app.html.eex見ると、中でrender/3を呼んでる
- つまり、ふつーに.eex内でrenderすれば、画面パーツをView込で呼べる・・・!
この最後のやつ。画面をパーツ化する時に、template以外はなんのパーツを使うか知らなくて良くなるね。
結構phoenix自体の作りに踏み込んだ説明もしてくれているので、原文読んでください。
The ErrorView
- エラーもErrorViewが使われて、templates/error/にeex置けば良い
- ステータスコード + .html.eex で呼ばれる
- 404.html.eex みたいな
- とはいえ、ErrorViewでエラーになると良くないので、程々に
- 手元でやった感じ、空っぽの500が返った
Rendering JSON
json返すだけならViewやtemplate無しでもいける
けど、返却用にデータを加工するところはControllerとは分けたほうが良いよね、という内容。
Viewで複数render作れば、良い感じに階層構造が作れるので、うまく使ってね、ということらしい。
def render("index.json", %{pages: pages}) do
%{data: render_many(pages, HelloWeb.PageView, "page.json")}
end
def render("show.json", %{page: page}) do
%{data: render_one(page, HelloWeb.PageView, "page.json")}
end
def render("page.json", %{page: page}) do
%{title: page.title}
end
この例では、以下を返すことになる。
- index.jsonの中身は複数のpage情報
- show.jsonでは単一のpage情報
以上Viewでした。
今までのフレームワークで不満だった点が良い感じに解消されていそうで良い感じ。
かつ、一つ一つのパーツもシンプルにできているようで、とても勉強になります。