Spec-Zone.ru › Phoenix

Assigns и шаблоны HEEx

Исходный код

Все данные в LiveView хранятся в сокете — серверной структуре данных под названием Phoenix.LiveView.Socket. Ваши собственные данные хранятся в поле assigns этой структуры. Данные сервера никогда не передаются клиенту, кроме тех, которые выводит шаблон.

Язык шаблонов Phoenix называется HEEx (HTML+EEx). EEx — это Embedded Elixir, механизм шаблонов строк на Elixir. Такие шаблоны либо хранятся в файлах с расширением .heex, либо создаются непосредственно в исходных файлах с помощью сигила ~H. Подробнее о синтаксисе HEEx можно узнать в документации по сигилу ~H.

Функции Phoenix.Component.assign/2 и Phoenix.Component.assign/3 помогают хранить эти значения. В LiveView к ним можно обращаться как к socket.assigns.name, а внутри шаблонов HEEx — как к @name.

В этом разделе мы расскажем, как LiveView сокращает объём данных, передаваемых по сети, учитывая взаимодействие assigns и шаблонов.

Отслеживание изменений

При первом отображении шаблона .heex клиенту отправляются все его статические и динамические части. Рассмотрим следующий шаблон:

<h1>{expand_title(@title)}</h1>

В нём есть две статические части — <h1> и </h1> — и одна динамическая часть, состоящая из expand_title(@title). При последующих отображениях этого шаблона статические части не отправляются повторно, а динамическая отправляется только в случае изменения.

Изменения отслеживаются с помощью assigns. Если assign @title изменится, LiveView выполнит динамические части шаблона, expand_title(@title), и отправит новое содержимое. Если @title не изменился, ничего не выполняется и не отправляется.

Отслеживание изменений также работает при обращении к полям map/struct. Рассмотрим этот шаблон:

<div id={"user_#{@user.id}"}>
  {@user.name}
</div>

Если @user.name изменится, а @user.id — нет, LiveView повторно отобразит только @user.name и не будет выполнять или повторно отправлять @user.id.

Отслеживание изменений работает и при отображении других шаблонов, если они тоже являются шаблонами .heex:

{render("child_template.html", assigns)}

А также при использовании функциональных компонентов:

<.show_name name={@user.name} />

Отслеживание assigns также означает, что в шаблоне НЕЛЬЗЯ выполнять операции напрямую. Например, если выполнить запрос к базе данных в шаблоне:

<%= for user <- Repo.all(User) do %>
  {user.name}
<% end %>

то Phoenix никогда не будет повторно отображать раздел выше, даже если количество пользователей в базе данных изменится. Вместо этого перед отображением шаблона нужно сохранить пользователей в assigns LiveView:

assign(socket, :users, Repo.all(User))

В целом, загрузка данных никогда не должна происходить внутри шаблона, независимо от того, используете ли вы LiveView. Разница в том, что LiveView обеспечивает соблюдение этой рекомендации.

Распространённые ошибки

При использовании сигила ~H или шаблонов .heex внутри LiveView важно помнить о нескольких распространённых ошибках.

Переменные

Из-за области видимости переменных LiveView отключает отслеживание изменений при использовании переменных в шаблоне. Исключение составляют переменные, введённые блочными конструкциями Elixir, например case, for, if и другими. Поэтому в шаблонах HEEx не следует писать код вроде этого:

<% some_var = @x + @y %>
{some_var}

Вместо этого используйте функцию:

{sum(@x, @y)}

Аналогично, не следует определять переменные в начале функции render для LiveView или LiveComponents. Поскольку LiveView не может отслеживать sum или title, при изменении любого из этих значений LiveView придётся повторно отобразить оба.

def render(assigns) do
  sum = assigns.x + assigns.y
  title = assigns.title

  ~H"""
  <h1>{title}</h1>

  {sum}
  """
end

Вместо этого используйте функции assign/2, assign/3, assign_new/3 и update/3 для вычисления этого значения. Любой assign, определённый или обновлённый таким способом, будет отмечен как изменённый, а другие assigns, например @title, LiveView продолжит отслеживать.

assign(assigns, sum: assigns.x + assigns.y)

Эти же функции можно использовать и внутри функциональных компонентов:

attr :x, :integer, required: true
attr :y, :integer, required: true
attr :title, :string, required: true
def sum_component(assigns) do
  assigns = assign(assigns, sum: assigns.x + assigns.y)

  ~H"""
  <h1>{@title}</h1>

  {@sum}
  """
end

В целом, старайтесь не обращаться к переменным внутри шаблонов HEEx, поскольку код, использующий переменные, выполняется при каждом отображении. Исключение составляют переменные, введённые блочными конструкциями Elixir, например if и for. Например, обращение к переменной post, определённой в приведённом ниже включении, работает как ожидается:

<%= for post <- @posts do %>
  ...
<% end %>

Переменная assigns

Раз уж речь зашла о переменных, стоит также обсудить специальную переменную assigns. Каждый раз, когда вы используете сигил ~H, необходимо определить переменную assigns, которая также доступна в каждом шаблоне .heex. Однако не следует обращаться к этой переменной напрямую внутри шаблонов — для доступа к отдельным ключам используйте @. Это относится и к функциональным компонентам. Рассмотрим несколько примеров.

Иногда может потребоваться передать все assigns из одного функционального компонента в другой. Например, представьте сложный компонент card с разделами заголовка, содержимого и подвала. Вы можете разбить его на три небольших внутренних компонента:

def card(assigns) do
  ~H"""
  <div class="card">
    <.card_header {assigns} />
    <.card_body {assigns} />
    <.card_footer {assigns} />
  </div>
  """
end

defp card_header(assigns) do
  ...
end

defp card_body(assigns) do
  ...
end

defp card_footer(assigns) do
  ...
end

Из-за особенностей работы функциональных компонентов с атрибутами приведённый выше код не будет отслеживать изменения и при каждом изменении будет повторно отображать все три компонента.

Как правило, не следует передавать все assigns. Вместо этого явно указывайте, какие assigns нужны дочерним компонентам:

def card(assigns) do
  ~H"""
  <div class="card">
    <.card_header title={@title} class={@title_class} />
    <.card_body>
      {render_slot(@inner_block)}
    </.card_body>
    <.card_footer on_close={@on_close} />
  </div>
  """
end

Если всё же необходимо передать все assigns, используйте обычный синтаксис вызова функции. Это единственный случай, когда допустимо обращаться к assigns внутри шаблонов:

def card(assigns) do
  ~H"""
  <div class="card">
    {card_header(assigns)}
    {card_body(assigns)}
    {card_footer(assigns)}
  </div>
  """
end

Так информация об отслеживании изменений передаётся от родительского компонента каждому дочернему, и повторно отображается только необходимое. Однако в целом лучше вообще не передавать assigns, а предоставить LiveView возможность самостоятельно выбрать оптимальный способ отслеживания изменений.

Изменение переменной assigns

Никогда не изменяйте переменную assigns в функциональном компоненте с помощью универсальных функций, таких как Map.put/3 или Map.merge/2. Вместо этого используйте Phoenix.Component.assign/2, Phoenix.Component.assign/3, Phoenix.Component.assign_new/3 или Phoenix.Component.update/3. Если изменить переменную assigns, например, с помощью Map.put/3, assigns внутри шаблона HEEx не будут обновляться после первоначального отображения. Для работы отслеживания изменений необходимо использовать специальные функции assign из LiveView.

Итак, никогда не делайте так:

def card(assigns) do
  assigns = Map.put(assigns, :sum, Enum.sum(assigns.values))

  ~H"""
  <p>{@sum}</p>
  """
end

Вместо этого делайте так:

def card(assigns) do
  assigns = assign(assigns, :sum, Enum.sum(assigns.values))

  ~H"""
  <p>{@sum}</p>
  """
end

Если здесь использовать Map.put/3 вместо assign/3, assign sum не будет обновлён, если изменить values после первоначального отображения шаблона HEEx.

Включения

HEEx поддерживает включения в шаблонах, позволяющие обходить списки и коллекции. Например:

<%= for post <- @posts do %>
  <section>
    <h1>{expand_title(post.title)}</h1>
  </section>
<% end %>

Или с помощью специального атрибута :for:

<section :for={post <- @posts}>
  <h1>{expand_title(post.title)}</h1>
</section>

Включения в шаблонах оптимизированы: статические части включения отправляются только один раз, независимо от количества элементов. Кроме того, LiveView отслеживает изменения в коллекции, переданной во включение. В идеальном случае, если изменится только одна запись в @posts, повторно будет отправлена только эта запись. По умолчанию для отслеживания изменений используется индекс. Это означает, что при вставке записи все записи после неё будут считаться изменёнными и отправятся повторно. Чтобы оптимизировать это поведение, в HEEx можно также передать тегам атрибут :key:

<section :for={post <- @posts} :key={post.id}>
  <h1>{expand_title(post.title)}</h1>
</section>

Подробнее об :key читайте в документации по sigil_H/2.

Для отслеживания изменений во включениях LiveView выполняет дополнительные операции учёта, требующие дополнительной памяти на сервере. Если потребление памяти вызывает беспокойство, рассмотрите возможность использования Phoenix.LiveView.stream/4, позволяющего управлять коллекциями без хранения их в памяти.

Итоги

Итак:

  1. Не определяйте локальные переменные в шаблонах HEEx, кроме как внутри конструкций Elixir.

  2. Не передавайте переменную assigns и не обращайтесь к ней внутри шаблонов HEEx.

  3. Для изменения переменной assigns используйте только специальные функции assign из LiveView.

← Предыдущая страница Добро пожаловать
Следующая страница → Развёртывание и восстановление

Пакет Hex Предпросмотр Hex (текущий файл) Просмотреть llms.txt Скачать версию ePub

Создано с помощью ExDoc (v0.40.3) для языка программирования Elixir

© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix-live-view.hexdocs.pm/assigns-eex.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API