客户端导航 客户端导航是一种无需完全重新加载页面即可在不同页面间跳转的技术。它并非由浏览器从服务器获取全新的 HTML 文档,而是获取新页面的内容,并仅更新 DOM 中发生变化的部分。这使得页面切换更快速、更流畅,带来类似应用程序的用户体验。 Interactivity API 通过 @wordpress/interactivity-router 包提供客户端导航功能。其核心概念是路由区域:这是页面中路由器在导航时知道如何更新的部分。您可以使用 data-wp-router-region 指令标记这些区域,当用户导航到新 URL 时,路由器会获取目标页面并仅替换匹配区域内的内容——页面上的其他所有内容保持不变。 Interactivity API 支持两种导航模式: 基于区域的客户端导航——在 WordPress 中实现客户端导航的推荐方法。 整页客户端导航(实验性)——将整个 元素视为单个区域,无需传统重新加载即可有效更新整个页面内容。本指南末尾的整页客户端导航(实验性)部分将对此进行介绍。 客户端导航的工作原理 当用户触发导航时(例如点击带有 data-wp-on--click 指令并调用 actions.navigate() 的链接),交互性路由器会: 获取新页面:路由器请求目标 URL 的 HTML。 解析响应:从获取的 HTML 中提取相关区域、样式、脚本和服务器渲染的数据。 更新 DOM:仅替换指定“路由器区域”内的内容为新内容。 更新浏览器历史记录:向浏览器的会话历史添加新条目(或在指定时替换当前条目)。 加载必要资源:渲染前加载新页面所需的任何新样式或脚本模块。 处理可访问性:进行屏幕阅读器播报以指示导航进度。 这种方法具有以下优点: 性能提升:仅更新页面中更改的部分,减少了数据传输和 DOM 操作。 状态保留:客户端状态(全局状态、本地上下文)在导航过程中得以保留。 平滑过渡:页面间无白屏闪烁;过渡感觉即时且类似应用。 SEO 友好:由于服务器仍渲染完整的 HTML 页面,搜索引擎可以正常抓取您的网站。 Interactivity Router 入门指南 @wordpress/interactivity-router 包自 WordPress 6.5 版本起已捆绑在核心中。如果您要开始一个新项目,最简单的设置方法是使用 @wordpress/create-block-interactive-template 脚手架工具,它会创建一个已配置好 Interactivity API 的区块: npx @wordpress/create-block@latest my-interactive-block --template @wordpress/create-block-interactive-template 无论您使用的是区块还是经典主题,添加客户端导航都涉及相同的步骤: 添加路由依赖:将 @wordpress/interactivity-router 添加为脚本模块的依赖项。 确保脚本模块在导航期间加载:标记您的脚本模块,以便路由器知道在新页面上加载它。 定义路由区域:使用 data-wp-router-region 属性标记应在导航期间更新的 HTML 元素。 触发导航:使用路由器的 actions.navigate() 函数以编程方式进行导航。 步骤 1 和 2 根据您使用的是区块还是经典主题而有所不同,将在下方详细介绍。无论您的设置如何,步骤 3 和 4 都是相同的。 添加路由依赖 @wordpress/interactivity-router 模块应作为动态依赖添加,以便仅在需要时获取。 对于区块,这可以通过在 view.js 文件中动态导入包来实现。区块构建工具( wp-scripts)会检测到动态导入并自动注册 PHP 端的依赖: const { actions } = yield import( '@wordpress/interactivity-router' ); yield actions.navigate( url ); 对于经典主题,无需依赖区块的 block.json,而是手动在 PHP 中注册并加入脚本模块队列,将 @wordpress/interactivity-router 列为动态依赖。同时,直接在主题模板文件中添加交互性 API 指令,并使用 wp_interactivity_process_directives() 处理它们,如服务端渲染指南所述。 // functions.php add_action( 'wp_enqueue_scripts', function () { wp_register_script_module( 'my-theme/navigation', get_template_directory_uri() . '/assets/navigation.js', array( '@wordpress/interactivity', array( 'id' => '@wordpress/interactivity-router', 'import' => 'dynamic', ), ) ); wp_enqueue_script_module( 'my-theme/navigation' ); } ); 确保脚本模块在导航期间加载 在客户端导航期间,路由器需要知道新页面应加载哪些脚本模块。它通过查找 当路由器获取页面时,它会扫描所有具有此属性且 loadOnClientNavigation 设置为 true 的 当路由器获取新页面时,它会从该页面提取导入映射,并将任何新的映射与当前页面的导入映射合并。这确保了即使在具有不同脚本集的页面之间导航时,脚本模块也能正确解析其依赖关系。 预加载脚本模块及其依赖项 预加载脚本模块需要解析其完整的依赖树,因为单个入口点模块可能依赖于数十个其他脚本模块,而这些模块又可能依赖于更多模块。 为了处理这个问题,路由器执行递归依赖解析: 它获取每个入口点脚本模块的源代码 它解析源代码以查找所有 import 语句 对于每个导入,它使用导入映射解析脚本模块说明符 它递归地获取并解析每个依赖项 此过程持续进行,直到依赖树中的所有脚本模块都被获取 路由器会智能地避免冗余工作。如果一个脚本模块已经被初始页面加载(它出现在初始导入映射中),路由器不会再次获取它——浏览器已经将其缓存。 处理导入时机 一个重要的细节是,脚本模块代码不应在实际导航发生前执行。路由器需要准备好脚本模块代码(以避免导航期间的延迟),但不应在用户仍在查看当前页面时运行该代码。 路由器通过转换获取的脚本模块来实现这一点。它重写源代码以使用 blob URL(直接嵌入 URL 中的数据),并缓存这些转换后的脚本模块。当导航发生时,路由器使用动态 import() 来执行缓存的脚本模块。 由于浏览器的模块系统按 URL 缓存脚本模块,多次导入相同的 blob URL 会返回相同的模块实例。这确保了每个脚本模块只执行一次,即使多个代码路径尝试导入它。 导航期间的脚本模块执行 当 navigate() 渲染新页面时,它会导入为该页面预加载的所有脚本模块: // 简化的概念视图,展示实际发生的过程。 for ( const moduleInfo of page.scriptModules ) { await import( moduleInfo.blobUrl ); } 每个脚本模块的顶层代码都会运行,这通常包括调用 store() 来注册操作、回调和状态。由于交互性 API 的存储是全局且可累加的,这些注册会与初始页面加载时已有的存储定义合并。 Server state and context Interactive elements often need data from the server — configuration values, content from the database, user preferences, and more. The Interactivity API provides three mechanisms for this: global state, local context and config. During client-side navigation, this server-provided data needs to be extracted from the new page and made available to the client-side code. How server data is embedded in pages When WordPress renders a page with interactive elements, it embeds server-provided data in special Local context is embedded directly in the data-wp-context attribute of elements:
Extracting state, context and config during fetch When the router fetches a new page, it extracts these types of server data: Global state: The router finds the