
Dioxus Router 完整指南为跨端 Dioxus 应用引入类型安全的嵌套路由与按路由分包【免费下载链接】dioxusFullstack app framework for web, desktop, and mobile.项目地址: https://gitcode.com/GitHub_Trending/di/dioxusDioxus Router 是 Dioxus 官方first-party的路由库为 web、桌面与移动端应用提供统一的导航与 URL 解析能力。本文以 packages/router/README.md 为主线结合dioxus-router源码与仓库内 06-routing 系列示例讲解如何用#[derive(Routable)]枚举声明路由、用Router/Outlet/Link组织页面并演示wasm-split按路由拆分 wasm bundle 的完整配置。读完你可以在自己的 Dioxus 工程里直接落地一套类型安全的嵌套路由方案。一、Dioxus Router 定位与适用前提从 packages/router/Cargo.toml 可见dioxus-router是独立分发的 crate描述为 Cross-platform router for Dioxus apps在 monorepo 中依赖dioxus-core、dioxus-history、dioxus-router-macro等内部包并提供四个 feature[features] default [html] streaming [dep:dioxus-fullstack-core] wasm-split [] html [dep:dioxus-html]也就是说默认启用html提供Link、历史按钮等依赖 HTML 语义的组件streaming与 fullstack 的流式渲染相关Router组件内部在开启该 feature 时使用use_after_suspense_resolved提交初始渲染块wasm-split用于按路由变体拆分 WebAssembly 产物是独立开关见下文专题。README 特别强调一条约束目标平台对应的 feature 不会被自动推断必须由你手动开启。在应用侧通常这样声明依赖[dependencies] dioxus { version *, features [router] }dioxus聚合 crate 的routerfeature 会引入dioxus-router如果你的代码需要直接use dioxus_router::...也可以显式添加dioxus-router依赖。二、从零起步用枚举描述一张“路由表”Dioxus Router 的核心思路与 React Router 相近但它借助 Rust 类型系统把“路径”和“页面组件”绑定在同一个枚举上每个变体对应一个 URL 模式变体字段就是 URL 中提取的动态参数。README 给出如下完整示例先原样落地再逐行拆解use dioxus::prelude::*; use std::str::FromStr; #[rustfmt::skip] #[derive(Clone, Debug, PartialEq, Routable)] enum Route { #[nest(/blog)] #[layout(Blog)] #[route(/)] BlogList {}, #[route(/:blog_id)] BlogPost { blog_id: usize }, #[end_layout] #[end_nest] #[route(/)] Index {}, } #[component] fn App() - Element { rsx! { Router::Route { } } } #[component] fn Index() - Element { rsx! { h1 { Index } Link { to: Route::BlogList {}, Go to the blog } } } #[component] fn Blog() - Element { rsx! { h1 { Blog } Outlet::Route { } } } #[component] fn BlogList() - Element { rsx! { h2 { List of blog posts } Link { to: Route::BlogPost { blog_id: 0 }, Blog post 1 } Link { to: Route::BlogPost { blog_id: 1 }, Blog post 2 } } } #[component] fn BlogPost(blog_id: usize) - Element { rsx! { h2 { Blog Post } } }把这段代码展开来看涉及三层要素#[derive(Routable)]让Route枚举具备“从 URL 解析成变体”和“把变体序列化成 URL”的双向能力同时承担页面渲染职责见下文对Routabletrait 的源码解读。注意派生要求类型实现Clone PartialEq Debug通常还有FromStr/Display的生成。Router::Route {}应用根组件读取当前 URL解析出对应的Route变体并把匹配的页面挂载出来。LinkOutletLink负责类型安全跳转Outlet是嵌套路由的“插槽”负责在布局组件内部渲染更深一层匹配到的内容。2.1 声明式语法速查route / nest / layout上面Route枚举展示了三组注解仓库内06-routing示例examples/06-routing/router.rs、examples/06-routing/simple_router.rs还覆盖了更多写法可归纳如下注解作用示例#[route(/)]把一个变体绑定到静态或含参数路径#[route(/:blog_id)] BlogPost { blog_id: usize }#[nest(/blog)] ... #[end_nest]为一组子路由统一加前缀/blog上例中BlogList实际为/blog/BlogPost为/blog/:blog_id#[layout(Blog)] ... #[end_layout]在子路由外层套一层布局组件布局内用Outlet渲染子页面Blog组件里的Outlet::Route {}动态段:name匹配单个路径段按字段类型解析:blog_id-usize静态段普通字符串字面段/blogURL 中被提取的字段会在导航时反序列化进变体因此访问/blog/42就会得到Route::BlogPost { blog_id: 42 }组件签名中同名参数blog_id: usize被直接注入。仓库 examples/06-routing/simple_router.rs 是同样结构的最小化版本可作为新建工程的起点模板。三、源码视角Routable、站点地图与 SegmentTypeREADME 只给了用法理解“为什么一个枚举能同时充当路由表与渲染器”需要进入实现。packages/router/src/routable.rs 定义了核心 traitpub trait Routable: FromStrErr: Display Display Clone static { /// 全站路由结构静态表编译期生成 const SITE_MAP: static [SiteMapSegment]; /// 在第 level 层渲染当前路由 fn render(self, level: usize) - Element; /// 判断该路由是否为另一路由的子路由 fn is_child_of(self, other: Self) - bool; /// 取父路由 fn parent(self) - OptionSelf; /// 摊平 SITE_MAP得到所有路径组合 fn flatten_site_mapa() - SiteMapFlatteneda; /// 列出全部静态路由不含动态参数 fn static_routes() - VecSelf; }由此可知#[derive(Routable)]由 packages/router-macro 提供宏会为枚举生成FromStr/Display实现并构造SITE_MAP——这是一个以SiteMapSegment为节点的类型擦除的站点结构树。节点内部存有SegmentType见 packages/router/src/routable.rs#[non_exhaustive] pub enum SegmentType { Static(static str), // 静态段如 /blog Dynamic(static str), // 动态段如 /:blog_id CatchAll(static str), // 全匹配段如 /:..rest Child, // 子路由/嵌套标记 }render(self, level)的level参数正是配合Outlet嵌套层级工作的一个页面上可以同时存在多个Outlet每个只渲染“与自身深度恰好一致”那一层的路由内容见 packages/router/src/components/outlet.rs 对行为与 panic 条件的描述static_routes()通过过滤掉动态段得到纯静态 URL 集合这一能力在 SSG、预取与wasm-split场景中都很关键对应地宏展开还会生成从 URL 单段构造类型的FromRouteSegment、从查询参数构造类型的FromQueryArgument、从 hash 片段构造类型的FromHashFragment等 trait 实现。它们遵循“类型实现FromStrDefault或Fromstr、Display即自动获得支持”的约定解析失败时多数回退到Default具体在 packages/router/src/routable.rs 各 trait 的实现处有文档级示例与测试如full_circle、to_route_segments两个单元测试验证了段落的往返一致性。值得一提的错误路径当 URL 无法匹配任何变体时RouteParseError会把所有尝试过的路由一一列出并显示匹配尝试序号attempted_routes: VecE方便排错见 packages/router/src/routable.rs。行为相关的集成测试集中在 packages/router/testsparsing.rs、site_map.rs、parent.rs读这些测试能快速理解宏展开后对边界 URL 的处理策略。四、Router、Outlet 与 Link三个组件的职责边界4.1Router路由上下文的提供者从 packages/router/src/components/router.rs 源码看RouterR其实非常薄它只做三件事创建并provide一个RouterContext把历史记录与当前 URL 暴露给整棵子树、提供一个OutletContext、然后直接渲染Outlet::R。它的RouterProps支持可选config回调返回RouterConfig用于自定义默认配置PartialEq恒为true目的是防止初始 URL 或配置变化触发重复渲染。Router通常必须包裹在HistoryProvider之内由后者注入实际的 History 实现。4.2Outlet嵌套布局的插槽只要在nest/layout下渲染页面实际是由“父层布局 子层内容”叠加出来的。每个布局组件需要在其内部放置一个Outlet::R子路由的内容会“落到”这里。比如上例中访问/blog/1时渲染结果为Blog布局里嵌着BlogPost。Outlet会感知自己被几层嵌套从而渲染深度恰好对应的那层内容在 debug 构建下若Outlet出现在任何Router之外会直接 panic见 packages/router/src/components/outlet.rs 文档release 下则静默不渲染——这与Link的降级策略一致。4.3Link类型安全导航与完整属性面Link对应渲染一个a但它会拦截点击交给路由器做客户端导航不会触发整页刷新。其 props 定义在 packages/router/src/components/link.rs 的LinkProps中属性含义to导航目标接受NavigationTarget见下文也可直接从Route::Xxx {}经into隐式转换class/active_class普通 class当目标路由与当前 URL 相同时追加的激活 classnew_tab为true时生成target_blank并在新标签打开onclick/onclick_only自定义点击处理onclick_onlytrue时只执行你的 handler、跳过内置导航onmounteda挂载后回调rel外部链接未指定时自动回退为noopener noreferrerattributes#[props(extends GlobalAttributes)]可透传id等全局属性从点击处理逻辑packages/router/src/components/link.rs可以看到它做了四层判断仅处理无修饰键的鼠标左键 →new_tab时交给浏览器默认行为 → 内部路由则prevent_default后调用router.push_any(to)→ 最后执行用户onclick。当前路由命中时Link还会自动添加aria-currentpage方便无障碍与导航高亮。SSR 输出形态可参考该文件内嵌的文档测试渲染为带class/rel/target/aria-current的标准a标签。4.4 导航目标Internal 与 External 的自动判别packages/router/src/navigation.rs 中的NavigationTargetR区分两类目标pub enum NavigationTargetR String { Internal(R), // 应用内部路由交给 Router 处理 External(String), // 外部 URL作为普通 href 处理 }从str/String转换时会先用url::Url::parse判定能解析成绝对 URL 的归为External否则尝试用R: FromStr解析为Internal解析不了时宁可退化为External也不会抛错。因此你可以放心地把“站外链接”也写进Link { to: https://... }它会被当作外部链接渲染——这是Link永不失效的兜底保证之一。五、导航 Hook 与参数化 URL 的类型系统除组件外crate 还按 packages/router/src/lib.rs 的模块划分导出三类常用 hook在hooks模块中见 packages/router/src/hooksuse_router()拿到当前RouterContext可在事件回调里以编程方式push/replace/go_back导航use_route()拿到当前解析出的路由值用于判断“我在哪一页”use_navigator()返回Navigator方便在不方便写Link的场景表单提交、倒计时跳转、登录成功后跳首页触发跳转。仓库 examples/06-routing/flat_router.rs、examples/06-routing/link.rs 与 examples/06-routing/router_resource.rs 分别展示了无嵌套扁平路由、Link全属性用法、以及把“当前路由”作为 signal 依赖触发异步数据加载的经典模式用路由参数驱动use_resource可以作为 hook 与响应式联动的直接范本。动态段之外的 URL 形态也有类型化支持#[route(/)]注解支持在同一路径里组合多类字段查询参数#[route(/edit?:blog_id)]、#[route(/?:q:page)]或#[route(/?:..query)]整体吞下整个 query string对应类型需要实现FromQueryArgument/FromQueryhash 片段#[route(/#:state)]把#后的内容交给FromHashFragment可实现“状态存 URL 但不触发服务器请求”仓库示例 examples/06-routing/hash_fragment_state.rs 就是该能力的完整演示rest 段#[route(/:..rest)]匹配多个剩余路径段。由于 URL 编码与解码是路由正确性的一部分crate 内部在 packages/router/src/lib.rs 定义了QUERY_ASCII_SET/PATH_ASCII_SET/FRAGMENT_ASCII_SET三套百分号编码字符集对齐 WHATWG URL 规范ToRouteSegments的默认实现见 packages/router/src/routable.rs会在序列化每个段时按PATH_ASCII_SET做percent_encoding::utf8_percent_encode——这意味着自定义类型里即便含有空格、{}等字符路由往返也是安全的。六、按路由拆分 bundleRouter 的 wasm-split 支持这是 README 的另一个核心主题。Dioxus Router 支持沿着路由变体自动做 bundle 拆分自动代码分割把不常访问的页面编译进懒加载 chunk只有用户真正导航到该路由时才通过网络拉取从而显著缩小首屏 wasm。6.1 开启方式拆分不是默认行为需要同时在两个地方显式打开wasm-splitfeature[dependencies] dioxus { version *, features [router, wasm-split] } dioxus-router { version *, features [wasm-split] }README 特别说明dioxus也必须开启wasm-split因为宏使用的是从 dioxus prelude 再导出的wasm-split能力对应 packages/router/Cargo.toml 中独立的wasm-split []feature。6.2 运行与打包约束开启拆分后调用图被切断每个路由 chunk 独立编译因此普通的dx serve无法运行拆分的应用。开发与预览时需要显式传参dx serve --experimental-wasm-split发布打包时README 推荐把 feature 仅在 bundling 阶段按需注入dioxus-router?/wasm-split的?语法表示“若该依赖存在才启用”避免污染常规依赖图dx bundle --features dioxus-router?/wasm-split --experimental-wasm-split6.3 一个必须遵守的组件约束懒加载意味着路由 chunk 在首次进入时会异步等待README 明确警告“router 会调用.suspend()因此你应在Outlet之上放置一个SuspenseBoundary以免 suspend 掉整个页面”。实践上就是在包含Outlet的布局外层套一层SuspenseBoundary配合 fallback 渲染加载占位例如rsx! { SuspenseBoundary { fallback: || rsx! { Loading page… }, Outlet::Route {} } }这一设计在仓库的 wasm-split 测试工程中也有体现可参考 packages/playwright-tests/wasm-split-harness 的配套配置与 packages/playwright-tests/wasm-split.spec.js 端到端断言观察懒加载 chunk 在浏览器中的实际请求行为。七、仓库中的配套示例与验证资源最小可运行模板examples/06-routing/simple_router.rs —— 一个Routable枚举 Router 两页面的骨架嵌套与布局examples/06-routing/router.rs —— 含nest/layout/Outlet的完整多层结构查询/hash 状态examples/06-routing/query_segment_search.rs 与 examples/06-routing/hash_fragment_state.rs滚动与资源恢复examples/06-routing/router_restore_scroll.rs路由切换恢复滚动位置、examples/06-routing/router_resource.rs单元/集成测试packages/router/testsURL 解析、站点地图与父路由关系以及 packages/router/src/routable.rs 内的文档测试可直接cargo test -p dioxus-router验证类型化往返与 SSR 渲染断言。结语综合 README 与源码可以看出Dioxus Router 的设计重点有三把路由表收敛进一个可派生的枚举让编译器替你检查链接与参数用nest/layout/Outlet表达任意深度的页面结构通过wasm-split把“按页面懒加载”做进路由层本身。实践时记住三个关键点即可少踩坑应用侧与dioxus-router的 feature 需按平台/构建模式手动开启使用拆分的应用必须走dx serve --experimental-wasm-split或dx bundle --features dioxus-router?/wasm-split --experimental-wasm-split懒加载页面上方务必放置SuspenseBoundary承接.suspend()。【免费下载链接】dioxusFullstack app framework for web, desktop, and mobile.项目地址: https://gitcode.com/GitHub_Trending/di/dioxus创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考