☰
Phoenix LiveView 服务端 Live Navigation 完全指南:patch、push_patch、navigate 与 handle_params 实战
2026/10/7 2:02:15 网站建设 项目流程
  • 后端
  • Web框架
  • WebSocket

【免费下载链接】phoenix_live_view

Rich, real-time user experiences with server-rendered HTML

项目地址:https://gitcode.com/gh_mirrors/ph/phoenix_live_view
点击查看免费下载

LiveView 基于浏览器的pushState历史 API 提供了"实时导航"(Live Navigation)能力:在切换页面时无需完整刷新页面,即可更新 URL 并渲染新内容。本文以 guides/server/live-navigation.md 为核心脉络,系统讲解客户端<.link patch/navigate>与服务端push_patch/2、push_navigate/2的使用场景、handle_params/3回调解读、replace历史记录控制,并结合本仓库源码剖析其底层实现与降级机制。读完本文,你将能在不引入任何前端路由框架的前提下,用纯服务端代码实现 SPA 级体验的页面切换、参数化排序与分页等常见需求。

Live Navigation 是什么

传统 Web 应用中,每次点击链接都会触发一次完整的 HTTP 请求与页面刷新。Phoenix LiveView 借助 浏览器 History API 中的 pushState,允许应用在不重载整个页面的情况下更新当前 URL 与页面内容,这一能力即被称为 Live Navigation。

它的核心价值在于:页面的骨架(布局、已经加载的资源、表单状态等)得以保留,只有真正变化的部分被以最小化的 diff 推送到客户端,从而获得接近原生应用的流畅体验,而这一切都在服务端渲染 HTML 的模式下完成。

两种触发方式

Live Navigation 可以从客户端触发,也可以从服务端直接发起。

从客户端触发:<.link patch={url}>与<.link navigate={url}>

在模板中使用Phoenix.Component.link/1组件,并传入patch或navigate属性即可。例如,传统写法是:

<.link href={~p"/pages/#{@page + 1}"}>Next</.link>

改用实时导航后写成:

<.link patch={~p"/pages/#{@page + 1}"}>Next</.link>

这里的~p是路径 sigil,用于构造带正确 URL 编码的路径。link/1组件在 lib/phoenix_component.ex 中的文档明确说明了三种属性语义:patch点击时 patch 当前 LiveView、navigate点击时导航到给定路径的新 LiveView、href则执行传统浏览器导航(普通<a>标签行为)。

从服务端触发:push_patch/2与push_navigate/2

在 LiveView 的事件处理函数中,可以返回带有导航指令的 socket:

{:noreply, push_patch(socket, to: ~p"/pages/#{@page + 1}")}

两个函数的源码位于 lib/phoenix_live_view.ex#L1175-L1226:

  • push_patch/2:在当前 LiveView 内部导航,立即调用handle_params/3,并把新状态推送给客户端,不重载页面且保持滚动位置;
  • push_navigate/2:导航到同一live_session中的另一个 LiveView,当前 LiveView 会被关闭、新 LiveView 被挂载,同样不重载页面。

值得注意的细节是,二者的底层选项解析共用同一个私有函数push_opts!/2(lib/phoenix_live_view.ex#L1235-L1240),它要求:to必须是本地路径,并依据:replace选项决定最终写入历史记录的方式是:push(压入新记录)还是:replace(替换当前记录)。最终通过put_redirect/2将导航指令写入 socket 的:redirected字段,随下一次渲染命令一并下发到客户端。若 socket 已被设置了其他重定向,put_redirect/2会直接抛出ArgumentError防止冲突(lib/phoenix_live_view.ex#L1242-L1248)。

此外,本地路径还会经过validate_local_url!/2的严格校验:拒绝以//开头的协议相对地址,并对\、/%09、\t、\n、\r等不安全字符抛出ArgumentError(lib/phoenix_live_view.ex#L1250-L1271),这是安全模型中的重要一环。

patch 与 navigate 的区别

选择patch还是navigate,取决于目标 LiveView 是否是当前已挂载的实例:

  • patch 操作:仅用于导航到当前 LiveView 自身,只更新 URL 与当前参数,不会挂载新的 LiveView。使用 patch 时handle_params/3回调会被调用,服务端仅把最小化的变更集(minimal diff)发送到客户端,同时保持滚动位置。
  • navigate 操作:用于卸载当前 LiveView 并挂载新的 LiveView。它只能在同一个 session内的 LiveViews 之间导航。重定向过程中,LiveView 会被加上phx-loading类,可据此向用户展示"正在加载新页面"的提示。

如果试图 patch 到另一个 LiveView,或 navigate 跨 live session,系统会自动降级为一次完整的页面刷新。这意味着即使应用结构发生了变化、导航逻辑没有及时更新,应用也依然能正常工作——这是 Live Navigation 内置的健壮性兜底。

三种导航方式速查

原文档给出了非常精炼的对比,整理如下:

方式对应 API行为特征
<.link href={...}>/redirect/2Phoenix.Controller.redirect/2基于 HTTP,处处可用,执行完整页面刷新
<.link navigate={...}>/push_navigate/2Phoenix.LiveView.push_navigate/2在同一 session 的 LiveViews 之间工作,挂载新 LiveView 但保留当前布局
<.link patch={...}>/push_patch/2Phoenix.LiveView.push_patch/2更新当前 LiveView,仅发送最小 diff,同时保持滚动位置

其中push_redirect/2在仓库中已被标记为@deprecated "Use push_navigate/2 instead"(lib/phoenix_live_view.ex#L1228-L1233),新代码请直接使用push_navigate/2。

handle_params/3回调:参数变更的处理入口

handle_params/3是 Live Navigation 的核心回调,它的调用时机包括:

  1. mount/3之后、首次渲染之前;
  2. 每次使用<.link patch={...}>或push_patch/2时。

它接收三个参数:请求参数(第一个)、URL(第二个)、socket(第三个)。

以原文档的用户表格为例:在路由中注册 LiveView:

live "/users", UserTable

在模板中添加实时排序链接:

<.link patch={~p"/users?sort_by=name"}>Sort by name</.link>

点击后,由于仍在当前 LiveView 内导航,handle_params/3被调用。关键原则是:绝不能信任传入的参数,必须在回调中校验用户输入再更新状态:

def handle_params(params, _uri, socket) do socket = case params["sort_by"] do sort_by when sort_by in ~w(name company) -> assign(socket, sort_by: sort_by) _ -> socket end {:noreply, load_users(socket)} end

这里的返回值为{:noreply, socket},:noreply表示不需要向客户端额外发送信息;但与其他handle_*回调一致,回调内部对状态的修改会触发一次新的服务端渲染。

从源码看,handle_params/3经由 lib/phoenix_live_view/lifecycle.ex#L199-L203 的handle_params/3函数派发:它从 socket 的私有字段中取出生命周期钩子列表,将(params, uri, acc)依次传给各钩子函数,任何钩子返回{:halt, ...}都会终止后续调用。这也解释了为何mount/3与handle_params/3会收到相同的参数——两者都源自同一次客户端请求的参数集。

mount 与 handle_params 的数据加载分工

既然handle_params/3收到的参数与mount/3完全相同,该如何决定数据加载的位置?原文档给出的通用规则是:

数据应始终在mount/3中加载,因为mount/3在整个 LiveView 生命周期内只调用一次;只有那些预期会通过<.link patch={...}>或push_patch/2变化的参数,才放到handle_params/3中加载。

原文档的博客分页示例很能说明问题:单篇博客的 URL 是/blog/posts/:post_id,页面上有分页评论,用户每次翻页时用<.link patch={...}>把 URL 更新为/blog/posts/:post_id?page=X。此时post_id在mount/3中读取,而评论的页码page则在handle_params/3中读取。这种分工让每次 patch 只重新加载确实变化的数据,避免无谓的重复查询。

replace:替换当前地址而不污染历史

LiveView 还支持替换当前浏览器 URL,而不是压入一条新记录。当某些事件需要改变 URL、但又不希望污染浏览器历史(例如避免用户疯狂点击"后退"却一直在同一页面内循环)时,给导航辅助函数传入replace选项即可:

<.link patch={~p"/users?sort_by=name"} replace>Sort by name</.link>
{:noreply, push_navigate(socket, to: "/", replace: true)}

replace选项默认值为false(即默认压入新历史记录),在push_patch/2、push_navigate/2的文档中均有说明(lib/phoenix_live_view.ex#L1185-L1189、lib/phoenix_live_view.ex#L1211-L1215),底层由push_opts!/2中的if opts[:replace], do: :replace, else: :push决定。<.link>组件同样支持replace属性,示例见 lib/phoenix_component.ex 中link/1的文档。

同一页面中的多个 LiveView

LiveView 允许通过在模板中调用Phoenix.Component.live_render/3在同一页面放置多个 LiveView。但需要注意:只有直接在路由(router)中定义的 LiveView才能使用本文所述的 Live Navigation 功能。这是因为 LiveView 与路由紧密协作——路由是导航合法性的依据,系统借此保证只能导航到已知路由,从而避免导航到不存在的地址。

仓库的 e2e 测试中可以看到典型用法,例如 test/e2e/support/form_live.ex 在事件处理中通过{:noreply, push_patch(socket, to: "/form?patched=true")}测试 patch 后的参数恢复,test/e2e/support/components_live.ex 则在handle_params/3中根据params["tab"]切换激活页签——这些都是文档所述机制在真实场景中的落地范本。

小结

Live Navigation 是 Phoenix LiveView 实现"服务端渲染 + 客户端流畅体验"的关键拼图:patch/push_patch面向当前 LiveView 的参数级更新,配合handle_params/3完成最小 diff 渲染;navigate/push_navigate负责在同一 session 内切换 LiveView 并保留布局;replace选项控制历史记录行为;而路由校验与完整刷新兜底则保证了应用的健壮性与安全性。掌握了这些机制,你就拥有了用纯服务端代码构建现代导航体验的完整工具箱。

  • 后端
  • Web框架
  • WebSocket

【免费下载链接】phoenix_live_view

Rich, real-time user experiences with server-rendered HTML

项目地址:https://gitcode.com/gh_mirrors/ph/phoenix_live_view
点击查看免费下载
上一篇:tymon/jwt-auth源码阅读路线:从入门到精通
下一篇:三步打造完美黑苹果:OpCore-Simplify终极OpenCore配置指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询