HEEx
Básico de HEEx
Read time: 7 minutes
Este guia é uma continuação direta do guia anterior
git clone https://github.com/adopt-liveview/v2-myapp.git --branch events-done.
Para facilitar o seu futuro no LiveView, vamos aprender algumas coisas simples sobre o funcionamento do HEEx que vão tornar seu dia a dia mais produtivo.
#Renderização de Elixir
Vamos atualizar o page_live.ex para isso:
defmodule MyappWeb.PageLive do
use MyappWeb, :live_view
def render(assigns) do
~H"""
<h2>Hello {"Lubien"}</h2>
<h2>Hello {1 + 1}</h2>
<h2>Hello {"Chris" <> " " <> "McCord"}</h2>
<h2>Hello <% "King Crimson" |> IO.puts() %></h2>
"""
end
end
Neste arquivo temos 3 interpolações HEEx e uma tag no nosso código. HEEx dá suporte a renderizar qualquer tipo de código que implemente o protocolo Phoenix.HTML.Safe.
-
O primeiro caso renderiza a string
"Lubien". - O segundo caso renderiza o inteiro 2.
-
O terceiro caso apenas usa o operador de concatenar strings
<>cujo resultado é "Chris McCord".
Mas e o quarto caso? Nada aparece na sua tela. O motivo é simples: usamos a tag <% %>, note que não existe um = após o primeiro %. Em HEEx isso significa "execute este código mas não renderize o resultado". Como ele utiliza a função IO.puts/2, você consegue ver o resultado no seu terminal.
Então eu posso adicionar lógica ao meu HEEx!
#Renderizando algo que não pode ser convertido em string
Agora vamos tentar isso:
defmodule User do
defstruct id: nil, name: nil
end
defmodule MyappWeb.PageLive do
use MyappWeb, :live_view
def render(assigns) do
~H"""
<h2>Hello {%User{id: 1, name: "Lubien"}}</h2>
"""
end
end
Você vai notar um "Internal Server Error" e a exceção no seu terminal:
[info] GET /
[debug] Processing with MyappWeb.PageLive.__live__/0
Parameters: %{}
Pipelines: [:browser]
[info] Sent 500 in 50ms
[error] ** (Protocol.UndefinedError) protocol Phoenix.HTML.Safe not implemented for User (a struct). This protocol is implemented for: Atom, BitString, Date, DateTime, Decimal, Duration, Float, Integer, List, NaiveDateTime, Phoenix.LiveComponent.CID, Phoenix.LiveView.Component, Phoenix.LiveView.Comprehension, Phoenix.LiveView.JS, Phoenix.LiveView.Rendered, Time, Tuple, URI
Got value:
%User{id: 1, name: "Lubien"}
(phoenix_html 4.3.0) lib/phoenix_html/safe.ex:1: Phoenix.HTML.Safe.impl_for!/1
(phoenix_html 4.3.0) lib/phoenix_html/safe.ex:15: Phoenix.HTML.Safe.to_iodata/1
(myapp 0.1.0) lib/myapp_web/live/page_live.ex:10: anonymous fn/2 in MyappWeb.PageLive.render/1
(phoenix_live_view 1.1.16) lib/phoenix_live_view/diff.ex:420: Phoenix.LiveView.Diff.traverse/6
(phoenix_live_view 1.1.16) lib/phoenix_live_view/diff.ex:146: Phoenix.LiveView.Diff.render/4
(phoenix_live_view 1.1.16) lib/phoenix_live_view/static.ex:291: Phoenix.LiveView.Static.to_rendered_content_tag/4
(phoenix_live_view 1.1.16) lib/phoenix_live_view/static.ex:171: Phoenix.LiveView.Static.do_render/4
(phoenix_live_view 1.1.16) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
(phoenix 1.8.1) lib/phoenix/router.ex:416: Phoenix.Router.__call__/5
(myapp 0.1.0) lib/myapp_web/endpoint.ex:1: MyappWeb.Endpoint.plug_builder_call/2
(myapp 0.1.0) deps/plug/lib/plug/debugger.ex:155: MyappWeb.Endpoint."call (overridable 3)"/2
(myapp 0.1.0) lib/myapp_web/endpoint.ex:1: MyappWeb.Endpoint.call/2
(phoenix 1.8.1) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
(bandit 1.8.0) lib/bandit/pipeline.ex:131: Bandit.Pipeline.call_plug!/2
(bandit 1.8.0) lib/bandit/pipeline.ex:42: Bandit.Pipeline.run/5
(bandit 1.8.0) lib/bandit/http1/handler.ex:13: Bandit.HTTP1.Handler.handle_data/3
(bandit 1.8.0) lib/bandit/delegating_handler.ex:18: Bandit.DelegatingHandler.handle_data/3
(bandit 1.8.0) lib/bandit/delegating_handler.ex:8: Bandit.DelegatingHandler.handle_continue/2
(stdlib 7.0.1) gen_server.erl:2424: :gen_server.try_handle_continue/3
(stdlib 7.0.1) gen_server.erl:2291: :gen_server.loop/4
Como mencionado anteriormente, o protocolo Phoenix.HTML.Safe é necessário para renderizarmos dados do Elixir. O motivo pelo qual esse protocolo existe é que o time do Phoenix converte o dado original do Elixir em uma estrutura chamada iodata, que é mais eficiente para ser enviada ao usuário.
Se você quiser apenas fazer um debug rápido de um dado que não pode ser renderizado pelo HEEx, a recomendação seria usar inspect: <h2>Hello {inspect(%User{id: 1, name: "Lubien"})}</h2>. Se você realmente precisar ensinar o HEEx a interpretar seu struct, pode também implementar o protocolo você mesmo. Atualizando o exemplo anterior:
defmodule User do
defstruct id: nil, name: nil
end
defimpl Phoenix.HTML.Safe, for: User do
def to_iodata(user) do
"User #{user.id} is named #{user.name}"
end
end
defmodule MyappWeb.PageLive do
use MyappWeb, :live_view
def render(assigns) do
~H"""
<h2>Hello {%User{id: 1, name: "Lubien"}}</h2>
"""
end
end
Usando defimpl/3 conseguimos definir o callback to_iodata/1 do protocolo e converter o usuário para string (algo que HEEx consegue renderizar).
Vale mencionar que se você decidir retornar qualquer tipo de HTML aqui, você fica responsável por garantir que não existe nenhuma vulnerabilidade como XSS. Imagine se seu usuário tem um nome com <svg onload=alert(1)> e você não escapou esse dado? Portanto, evite esta prática sempre que possível.
#Renderização de nil
Vamos simplificar nosso código para apenas isso:
defmodule MyappWeb.PageLive do
use MyappWeb, :live_view
def render(assigns) do
~H"""
<h2>Hello {"Lubien"}</h2>
<h2>Hello {nil}</h2>
"""
end
end
Neste cenário vemos que quando a interpolação {} recebe nil o resultado é não renderizar absolutamente nada. Isso será útil logo mais!
#Renderização de atributos HTML
Agora tente isto:
defmodule MyappWeb.PageLive do
use MyappWeb, :live_view
def render(assigns) do
bg_for_hello_phoenix = "bg-black"
multiple_attributes = %{style: "color: yellow", class: "bg-black"}
~H"""
<style>
.color-red { color: red }
.bg-black { background-color: black }
.bg-red { background-color: red }
</style>
<h2 style="color: red" class="bg-black">Hello World</h2>
<h2 style="color: white" class={"bg-" <> "red"}>Hello Elixir</h2>
<h2 class={[bg_for_hello_phoenix, nil, "color-red"]}>Hello Phoenix</h2>
<h2 {multiple_attributes}>Hello LiveView</h2>
"""
end
end
Existem múltiplas maneiras de adicionar atributos HTML em HEEx para a conveniência do desenvolvedor. Vamos verificar cada um deles.
No primeiro caso (Hello World) adicionamos style="color: red" que funciona como qualquer outro HTML no mundo. Neste formato não há nenhum tipo de processamento extra. Em class={"bg-black"}, ao usar chaves estamos dizendo que o conteúdo dentro delas é um código Elixir. Qualquer código Elixir como class={calculate_class()} (supondo que a função exista) ou class={"bg-#{@my_background}"} (supondo que o assign exista) será válido!
No segundo caso (Hello Elixir) apenas demonstramos mais uma vez o que foi explicado no caso anterior. Em class={"bg-" <> "red"} pode-se notar um exemplo de usar o operador <> para calcular a classe final.
No terceiro exemplo (Hello Phoenix) existe uma dica de ouro: você pode passar uma lista com múltiplas strings para um atributo e ao final ela será automaticamente unida, e os valores que forem nil serão ignorados. O motivo que torna essa técnica poderosa é que facilita trabalhar com variáveis, como podemos ver a bg_for_hello_phoenix sendo utilizada.
O último caso (Hello LiveView) adiciona mais uma forma de trabalhar com atributos. Se você precisar algum dia adicionar atributos de modo dinâmico, isto é, você não sabe exatamente quais atributos vão ou não entrar de antemão, pode usar a sintaxe de adicionar um mapa do Elixir dentro da tag de abertura do HTML e o HEEx vai entender que cada chave do mapa representa um atributo.
Posso usar variáveis na minha render function?
#Uma observação sobre uma sintaxe antiga
Antes das interpolações como {this} existirem, a forma padrão do HEEx renderizar coisas em HTML era a tag <%= this %>. Similar à tag <% %> (que não renderiza), esta vem com um =. Hoje em dia, se você usar formatadores em projetos novos, elas serão automaticamente convertidas para interpolações a menos que se enquadrem em casos específicos. Ainda vamos usar <%= %> no futuro para estruturas de controle!
#Resumindo!
-
Usar a tag
{}renderiza código Elixir que o protocoloPhoenix.HTML.Safeaceita. -
Usar a tag
<% %>apenas executa código Elixir e não renderiza nada. -
Você pode implementar
Phoenix.HTML.Safepara structs, mas deve estar ciente dos riscos de segurança que isso pode trazer. -
HEEx considera
nilcomo algo que não deve ser renderizado; isso é útil caso você queira trabalhar com variáveis opcionais. - Em HEEx, atributos HTML com o valor entre chaves executam qualquer código Elixir válido para gerar o valor do atributo.
- Em HEEx, você também pode passar listas para atributos para simplificar a mistura de strings e variáveis.
- Em HEEx, você pode passar um mapa entre chaves na tag HTML para que múltiplos atributos sejam adicionados de forma dinâmica.
-
Projetos antigos usavam a sintaxe de interpolação
<%= %>ao invés de{}. -
<%= %>é usado principalmente para estruturas de controle (if,else...).
Feedback
Got any feedback about this page? Let us know!