LiveView 0.18 发布

发布于 2022 年 9 月 21 日,作者 Chris McCord


我们一直在为 LiveView 0.18.0 打造一些颠覆性的功能。声明式赋值和插槽提供了编译时警告和增强文档,使构建自己的 UI 或使用 UI 库变得非常愉快。这些新功能将函数组件提升到一个新的水平,提供了一个真正一流的可组合组件系统。

此外,新的开箱即用焦点组件和 JS 命令提供了可访问性改进,以确保 LiveView 应用程序对所有用户都能很好地工作。我们还发布了一个新的 mix 格式化程序插件,用于格式化 HEEx 模板,使用后你就会离不开它。

要了解这些功能为什么如此重要,让我们来看一个简单的函数组件。假设我们的应用程序中有一个 modal 组件,我们想要调用它。在函数组件之前,你会这样编写你的 Elixir 模板:

<div>
  <%= modal(title: "Your file is ready!") do %>
    The file will only be available for 10 minutes
  <% end %>
</div>

这很简单,但问题在于,当我们想要在模态标题中放置任意内容时,例如指向下载的链接,我们最终会尝试将原始 HTML 字符串连接在一起,这根本行不通

<div>
  <%= modal(title: "Your #{"<a href=\"#{@url}\" download>file</a>"} is ready!") do %>
    The file will only be available for 10 minutes
  <% end %>
</div>

这会失败,因为 Phoenix 会对字符串执行 HTML 转义,所以我们需要仔细地解包自己的 HTML 输入。标准模板模型在可组合性方面存在缺陷。函数组件和插槽通过允许组件声明命名插槽来解决这个问题,调用者可以在这些插槽中提供任意内容到命名部分,例如模态标题、标题或页脚。让我们用函数组件和插槽重写上面的代码

<div>
  <.modal>
    <:title>
      Your <.link href={@url} download>file</.link> is ready!
    </:title>
    The file will only be available for 10 minutes
  </.modal>
</div>

现在我们可以开始看到函数组件和插槽的真正威力。函数组件在标记中很好地组合在一起,插槽允许调用者提供他们自己的任意结构,比如上面的 <:title> 插槽。这里我们正在传递我们自己的标记内容,包括对其他函数组件的调用!这使得封装的 UI 构建块可以组合在一起。不再需要任意字符串连接或针对每个用例的定制严格模板。

插槽提供了更强大的组合功能。插槽不是将单个命名事物放置在组件中,而是集合,这允许调用者向组件提供任意数量的插槽条目。例如,想象一个表格组件,其中调用者需要提供任意数量的表格列。这在使用常见的静态 HTML 模板抽象时无法干净地组合,但使用插槽,阅读和编写就变得非常美观

<.table id="files" rows={@files}>
  <:col :let={file} label="Name"><%= file.name %></:col>
  <:col :let={file} label="Size"><%= file.size %></:col>
  <:action :let={file}>
    <.link patch={~p"/files/#{file}/edit"}>Edit</.link>
  </:action>
  <:action :let={file}>
    <.link phx-click={JS.push("delete", value: %{id: file.id})} data-confirm="Are you sure?">
      Delete
    </.link>
  </:action>
</.table>

与其在所有地方编写原始 <table> 标签或定制函数,比如 <%= file_table(rows: @files)>,我们可以在应用程序中利用插槽定义一个单一的 <.table> 函数组件,该组件接受一个 <:col><:action> 插槽。注意我们上面传递了多个列和操作?这是插槽的一项美妙功能。在内部,表格可以根据我们提供的每个 <:col> label 渲染 <thead><th> 条目。接下来,要渲染每一行,组件只需遍历我们传递的每一行,并根据每个 <:col> 来渲染一个列和内部内容。这使得编写标记比编写原始 HTML 和复制粘贴样式和标签更令人愉快且可重用。你会发现,一旦在应用程序中建立了一套核心 UI 组件,你很少需要用新功能扩展它们,因为组件和插槽的组合。

这一切看起来都很棒,但随着时间的推移,随着添加了更多属性和插槽,我们如何发现实际支持哪些?我们对使用这些组件有多大的信心?声明式赋值和插槽出现了,编译器会在你的背后默默支持你。

声明式赋值和插槽

感谢 Marlus Saraiva 在 Surface 库中开创了这些功能,然后将其贡献给 LiveView,组件现在已经提升到了可用性和生产力的一个新的水平。

要了解如何实现,让我们回到我们的模态。假设我们希望在模态在页面加载时渲染时自动显示它,而不是稍后以编程方式显示它。假设团队中的某个人对该函数进行了很好的文档化,我们可以去探查,也许可以找到一个使用 show 属性自动显示模态的示例。但即使是最好的文档也无法让我们免受拼写错误或属性或插槽不正确的影响。例如,想象一下我们错误地传递了 <.modal autoshow>

幸运的是,编译器会在你的背后默默支持你!在这里我们看到我们的 autoshow 是一个未知属性,我们了解到我们遗漏了必需的 id 属性。我们在编辑器中实时获得此反馈,而不是等到运行时出现错误。

通过声明式赋值和插槽,函数组件会指定它们接受的属性、类型和插槽,以及内联文档。这不仅提供了增强的文档,而且组件的每次调用都将在编译时进行验证,以提供反馈。不再需要运行时陷阱或猜测。这也允许社区发布一流的 UI 库,用户可以在构建应用程序时立即上手。下面是实际应用中的示例

HEEx HTML 格式化程序

感谢 Feelipe Renan 的出色工作,你的 .heex 文件和应用程序中的任何 ~H 模板现在在运行 mix format 时将被格式化为 HTML。这特别有用,因为你的标记和函数组件嵌入了 Elixir 表达式,你希望这些表达式按照与其他 Elixir 代码相同的规则进行格式化。HEEx 格式化程序处理所有这些情况 - 格式化通用标记、格式化标签中的 Elixir 表达式以及格式化 EEx 内容 <%= %> 中的 Elixir 表达式。格式化程序还确保前端和后端开发人员在整个应用程序中遵循统一的编码指南。

让我们看看它是如何工作的

可访问性

LiveView 应该为所有用户提供出色的 Web 体验,包括有可访问性需求的用户,例如屏幕阅读器用户。LiveView 0.18 包含一些帮助实现此目标的基元,更多功能即将推出。

首先,我们发布了一个新的 <.focus_wrap> 组件,它允许你只需将任何模板内容包装在 <.focus_wrap>...</.focus_wrap> 中,就可以让 Tab 焦点回到该元素。这听起来可能并不令人兴奋,但对于使用键盘导航的屏幕阅读器用户来说,在显示对话框和模态时,这是一个必不可少的基元。这也是一个需要开发人员使用自定义 JavaScript 干预才能实现的功能。让我们看看它是如何工作的

我们还发布了用于处理焦点状态的新 JS 命令,包括 JS.focusJS.focus_first。两者都允许你以编程方式设置元素的焦点,但 JS.focus_first 特别好,因为它是一个设置后就可以忘记的命令,它会从可访问性角度做正确的事情。它只需找到容器内的第一个可聚焦元素并设置焦点,而无需你操心。例如,当弹出模态时,你不需要考虑模态是在一种情况下包含表单输入,还是在另一种情况下包含确认/取消按钮。只需使用该命令来查找要聚焦的第一个元素,LiveView 会在那里设置焦点。

你的显示模态函数可能看起来像这样

def show_modal(js \\ %JS{}, id) when is_binary(id) do
  js
  |> JS.show(to: "##{id}")
  |> JS.show(to: "##{id}-bg")
  |> show("##{id}-container")
  |> JS.focus_first(to: "##{id}-container")
end

现在,应用程序中任何显示模态的地方都会在模态中设置焦点。结合 <.focus_wrap>phx.new 将生成的 ARIA 标签,屏幕阅读器用户在使用你的应用程序时将获得一个开箱即用的完全可访问的模态。

死视图中的 JS 命令和钩子

JS 命令是另一个声明式 LiveView 功能,它允许你在不访问服务器的情况下操作客户端上的 DOM。它们对于显示模态和下拉列表、分派事件、动画内容和切换属性特别有用,但到目前为止,它们一直是 LiveView 特定的功能。常规静态视图也共享这些相同的用例,其中人们希望显示或隐藏内容,而无需定制 JavaScript 或引入框架。LiveView 0.18 现在包括对在 LiveView 之外渲染的内容中使用 JS 命令和钩子的支持。这使你的许多核心 UI 函数组件可以在 LiveView 或常规视图中使用,例如模态、闪存消息和下拉列表。

我们对这个版本及其带来的生产力改进感到非常兴奋,我们迫不及待地想在即将推出的 Phoenix 1.7 应用程序中展示它所支持的所有精妙功能。

敬请关注,祝您编码愉快!

–Chris