Компоненты и HEEx
Исходный кодТребование: предполагается, что вы ознакомились с вводными руководствами и запустили приложение Phoenix.
Требование: предполагается, что вы ознакомились с руководством по жизненному циклу запроса.
Конвейер конечной точки Phoenix принимает запрос, направляет его контроллеру и вызывает модуль представления для отображения шаблона. Интерфейс представления со стороны контроллера прост: контроллер вызывает функцию представления с назначенными значениям соединения, а задача этой функции — вернуть шаблон HEEx. Любую функцию, которая принимает параметр assigns и возвращает шаблон HEEx, мы называем функциональным компонентом.
Фреймворк Phoenix предназначен для HTML-приложений, JSON API, конечных точек GraphQL и т. д. Поэтому все возможности, связанные с отображением HTML, входят в состав двух отдельных пакетов:
phoenix_html— определяет строительные блоки для безопасного создания HTML. В проекте вы будете главным образом взаимодействовать с модулемPhoenix.HTML, который импортируется по умолчанию во все шаблоны
phoenix_live_view— библиотека для создания насыщенных интерактивных интерфейсов реального времени на основе HTML, отображаемого на сервере. LiveView предоставляет несколько абстракций для создания совместных и динамических приложений, а также определяет язык шаблоновHEEx, функциональные компоненты и JS-команды, предлагая мощные абстракции для любых приложений с HTML, отображаемым на сервере
В этой главе мы повторим, как используются компоненты, и подробнее рассмотрим новые варианты их применения.
Функциональные компоненты
Функциональные компоненты — основа любого отображения шаблонов на основе разметки в Phoenix. Они служат общей абстракцией для стандартных приложений с контроллерами MVC, приложений LiveView, макетов и небольших элементов интерфейса, используемых в других шаблонах. Их документация доступна в модуле Phoenix.Component.
В конце главы о жизненном цикле запроса мы создали шаблон в lib/hello_web/controllers/hello_html/show.html.heex. Откроем его:
<Layouts.app flash={@flash}>
<section>
<h2>Hello World, from {@messenger}!</h2>
</section>
</Layouts.app>
<Layouts.app> — это функциональный компонент, определённый внутри lib/hello_web/components/layouts.ex. Если открыть этот файл, вы увидите:
def app(assigns) do
~H"""
<header class="navbar px-4 sm:px-6 lg:px-8">
...
До сих пор все определённые нами функциональные компоненты также могли использоваться как шаблоны. Давайте узнаем о них больше и определим собственный компонент, чтобы инкапсулировать HTML-разметку.
Представим, что мы хотим переработать show.html.heex и вынести отображение <h2>Hello World, from {@messenger}!</h2> в отдельную функцию. Помните, что show.html.heex встроен в модуль HelloHTML. Откроем его:
defmodule HelloWeb.HelloHTML do use HelloWeb, :html embed_templates "hello_html/*" end
Всё довольно просто. Всего две строки: use HelloWeb, :html. Эта строка вызывает функцию html/0, определённую в HelloWeb, которая настраивает базовые импорты и конфигурацию для наших функциональных компонентов и шаблонов. Все импорты и псевдонимы в нашем модуле также будут доступны в шаблонах. Аналогично, если мы хотим написать функциональный компонент, который будет вызываться из show.html.heex, достаточно добавить его в HelloHTML. Давайте так и сделаем:
defmodule HelloWeb.HelloHTML do
use HelloWeb, :html
embed_templates "hello_html/*"
attr :messenger, :string, required: true
def greet(assigns) do
~H"""
<h2>Hello World, from {@messenger}!</h2>
"""
end
end
С помощью макроса Phoenix.Component.attr/3 мы объявили принимаемые атрибуты, а затем определили функцию greet/1, возвращающую шаблон HEEx.
Теперь нужно обновить show.html.heex:
<Layouts.app flash={@flash}>
<section>
<.greet messenger={@messenger} />
</section>
</Layouts.app>
После перезагрузки http://localhost:4000/hello/Frank мы должны увидеть то же содержимое, что и раньше! Теперь show.html.heex вызывает два разных функциональных компонента:
<Layouts.app— синтаксис вызова функциональных компонентов, определённых в отдельном модуле; он подчиняется тем же правилам, что и вызов любой другой функции в Elixir<.greet— если функциональный компонент определён в том же модуле, что и шаблон, имя модуля можно опустить и вызвать компонент, добавив точку перед его именем
Поскольку шаблон show.html.heex встроен в модуль HelloHTML, мы смогли вызвать функциональный компонент напрямую как <.greet messenger="..." />. Если бы компонент был определён в другом месте, пришлось бы указать его полное имя, например: <HelloWeb.HelloHTML.greet messenger="..." />.
Если объявить атрибуты обязательными, Phoenix предупредит, если мы вызовем компонент <.greet />, не передав атрибуты. Если атрибут необязательный, можно указать параметр :default со значением:
attr :messenger, :string, default: nil
В целом функциональные компоненты — основа системы отображения Phoenix. Теперь разберёмся во всех возможностях языка шаблонов HEEx.
HEEx
Функциональные компоненты и файлы шаблонов используют язык шаблонов HEEx, название которого расшифровывается как «HTML + Embedded Elixir». Мы можем писать код Elixir внутри {...} для интерполяции с учётом HTML в атрибутах тегов и теле документа, как мы делали выше. Например, с помощью @name мы обращаемся к ключу name, определённому внутри assigns.
Также можно интерполировать произвольные блоки HEEx с помощью <%= ... %>. Это часто используют для блочных конструкций. Например, для условных выражений:
<%= if some_condition? do %>
<p>Some condition is true for user: {@messenger}</p>
<% else %>
<p>Some condition is false for user: {@messenger}</p>
<% end %>
или даже циклов:
<table>
<tr>
<th>Number</th>
<th>Power</th>
</tr>
<%= for number <- 1..10 do %>
<tr>
<td>{number}</td>
<td>{number * number}</td>
</tr>
<% end %>
</table>
В HEEx также есть удобные расширения HTML, которые мы рассмотрим далее.
Расширения HTML
Помимо интерполяции выражений Elixir, шаблоны .heex поддерживают расширения с учётом HTML. Например, посмотрим, что произойдёт, если попытаться интерполировать значение с символами «<» или «>», что может привести к внедрению HTML:
{"<b>Bold?</b>"}
После отображения шаблона на странице вы увидите буквальный текст <b>. Это означает, что пользователи не могут внедрить HTML-содержимое на страницу. Если вы хотите разрешить им это, можно вызвать raw, но будьте крайне осторожны:
{raw("<b>Bold?</b>")}
Ещё одна мощная возможность шаблонов HEEx — проверка HTML и синтаксиса интерполяции атрибутов. Можно написать:
<div title="My div" class={@class}>
<p>Hello {@username}</p>
</div>
Обратите внимание, что можно просто использовать key={value}. HEEx автоматически обработает особые значения, например false, чтобы удалить атрибут, а также списки классов.
Чтобы интерполировать динамическое число атрибутов из списка ключевых слов или карты, используйте:
<div title="My div" {@many_attributes}>
<p>Hello {@username}</p>
</div>
Попробуйте также удалить закрывающий </div> или переименовать его в </div-typo>. Шаблоны HEEx сообщат об ошибке.
HEEx также поддерживает сокращённый синтаксис для выражений if и for с помощью специальных атрибутов :if и :for. Например, вместо такого варианта:
<%= if @some_condition do %> <div>...</div> <% end %>
можно написать:
<div :if={@some_condition}>...</div>
Аналогично, выражения comprehensions можно записать так:
<ul>
<li :for={item <- @items}>{item.name}</li>
</ul>
CoreComponents
В новом приложении Phoenix в папке components также будет модуль core_components.ex. Это отличный пример определения функциональных компонентов для повторного использования во всём приложении. Благодаря этому по мере развития приложения компоненты будут выглядеть единообразно.
Если заглянуть в def html в HelloWeb, размещённый в lib/hello_web.ex, вы увидите, что CoreComponents автоматически импортируются во все HTML-представления через use HelloWeb, :html. Поэтому в самом CoreComponents используется use Phoenix.Component, а не use HelloWeb, :html в начале файла: второй вариант привёл бы к взаимной блокировке, поскольку мы попытались бы импортировать CoreComponents в сам модуль.
CoreComponents также играют важную роль в генераторах кода Phoenix, поскольку генераторы предполагают наличие этих компонентов, чтобы быстро создавать заготовки приложения. Чтобы подробнее ознакомиться со всеми этими возможностями, вы можете:
Изучить сгенерированный модуль
CoreComponentsи ознакомиться с практическими примерамиПрочитать официальную документацию по
Phoenix.ComponentПрочитать официальную документацию по HEEx и сигилам ~H
Если вам нужны более высокоуровневые компоненты, чем минимальный набор, включённый в Phoenix, проект LiveView ведёт список систем компонентов
Макеты
При обсуждении компонентов и отображения в Phoenix важно понимать концепцию макетов.
Во всех приложениях Phoenix есть компонент, называемый «корневым макетом». Именно здесь находятся теги <head> и <body> HTML-страницы. Корневой макет настраивается в файле lib/hello_web/router.ex:
plug :put_root_layout, html: {HelloWeb.Layouts, :root}
В только что сгенерированном приложении шаблон находится в lib/hello_web/components/layouts/root.html.heex. Откройте его и ближе к концу <body> вы увидите следующее:
{@inner_content}
Здесь размещаются наши шаблоны после отображения. Корневой макет используется как контроллерами, так и LiveView.
Любая динамическая функциональность приложения реализуется в виде функциональных компонентов. Например, меню приложения и боковая панель обычно входят в компонент app в lib/hello_web/components/layouts.ex, который вызывается в каждом шаблоне:
<Layouts.app flash={@flash}>
...
</Layouts.app>
Этот механизм также очень гибок. Например, чтобы создать макет для администратора, достаточно добавить новую функцию в модуль Layouts, а затем вызвать Layouts.admin вместо Layouts.app:
<Layouts.admin flash={@flash}>
...
</Layouts.admin>
В предыдущих версиях Phoenix использовался механизм вложенных макетов:
:layoutsпередавался вPhoenix.Controller, а:layout— вPhoenix.LiveView. В новых приложениях Phoenix этот механизм не рекомендуется.
© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix.hexdocs.pm/components.html