单文件组件框架中,当更改请求地址时,并不会引发页面跳转,而是由框架捕获请求地址(在框架中我们称之为路由),然后根据路由与组件的映射关系,在页面的指定位置切换和显示组件。在哪个位置显示(称之为路由出口),就是布局要解决的问题。无论是Vue,还是Blazor,我们都能以母版页的方式来理解和使用布局。母版页,就是布局的整个页面环境,我们可以在母版页的指定位置,放一个占位符,这个占位符作为路由出口,Vue使用【<router-view />】组件作为占位符,而Blazor中使用【@Body】指令。母版页本质上也是组件,所以我们可以将“在路由出口”显示的组件也成为母版页,这样就可以实现复杂的嵌套布局。文字描述,比较晦涩,下面通过案例直接上手。
一、案例说明:如图所示,首屏为左右结构,左侧显示文字链接“关于”和“动物”,右侧是一个路由出口。点击“关于”时,直接在路由出口上显示“About组件”。点击“动物”时,切换到Animal组件,Animal组件也是一个母版页,上下结构,上方显示文字链接“PandaDetail”和DogDetail“”,下方是动物组件的路由出口,点击上方链接时,将切换显示“PandaDetail组件”和“DogDetail组件”。
二、Blazor实现(忽略CSS样式)。文件结构包括:App.razor(路由管理器并设置顶层母版)、MainLayout.razor(默认顶层母版)、Index.razor(首页)、About.razor、Animal.razor(二层母版)、PandaDetail.razor、DogDetail.razor。
1、App.razor(路由管理器并设置顶层母版)
<!--创建项目时自动创建立,基本没有变化,对布局来说,主要是指定的默认母版页ManiLayout--> <Router AppAssembly="@typeof(App).Assembly"> <Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> <FocusOnNavigate RouteData="@routeData" Selector="h1" /> </Found> <NotFound> <PageTitle>Not found</PageTitle> <LayoutView Layout="@typeof(MainLayout)"> <p role="alert">抱歉,未找到请求的页面.</p> </LayoutView> </NotFound> </Router>
2、MainLayout.razor(默认顶层母版)
<!--将本组件转为母版页,LayoutComponentBase提供了Body属性 Body为RenderFragment类型,作为占位符渲染子组件,称之为路由出口--> @inherits LayoutComponentBase <PageTitle>嵌套布局</PageTitle> <div class="page"> <div class="sidebar"> <NavLink class="nav-link" href="" Match="NavLinkMatch.All"> <h1>首页</h1> </NavLink> <NavLink class="nav-link" href="about"> <h1>关于</h1> </NavLink> <NavLink class="nav-link" href="animal"> <h1>动物</h1> </NavLink> </div> <main> @Body </main> </div>
3、Index.razor(首页,本案例中没有实际意义)
<!--路由为首页,隐式使用默认母版页MainLayout--> @page "/" <PageTitle>首页</PageTitle> <h1>这里是首页</h1> @code{ }
4、About.razor
<!--路由为about,隐式使用默认母版页MainLayout--> @page "/about" <PageTitle>关于</PageTitle> <h3>这里是关于页About.razor</h3> @code { }
5、Animal.razor(二层母版)
<!--路由为animal,使用母版页MainLayout。 注意,如果组件转为母版页,需要显式指定母版页,不能隐式使用默认母版页。--> @page "/animal" @layout MainLayout <!--将组件转为母版页,可以使用@Body占位符--> @inherits LayoutComponentBase <PageTitle>动物</PageTitle> <h3>这里是动物页Animal.razor</h3> <NavLink class="nav-link" href="animal/panda-detail"><h5>熊猫详情</h5></NavLink> <NavLink class="nav-link" href="animal/dog-detail"><h5>狗狗详情</h5></NavLink> <main> @Body </main> @code { }
6、PandaDetail.razor、DogDetail.razor,Animal的子页面
<!--PandaDetail.razor--> <!--路由animal/panda-detail,母版页为Animal.razor--> @page "/animal/panda-detail" @layout Animal <h3>这里是熊猫详情页PandaDetail.razor</h3> @code { } <!--DogDetail.razor--> <!--路由animal/dog-detail,母版页为Animal.razor--> @page "/animal/dog-detail" @layout Animal <h3>这里是狗狗详情页DogDetail.razor</h3> @code { }
三、Vue的实现(忽略CSS样式)。文件结构包括:Router/index.js(路由管理器)、App.vue(顶层母版)、About.vue、Animal.vue(二层母版)、PandaDetail.vue、DogDetail.vue。
1、App.vue(顶层母版)
<template> <header> <h1><router-link to="/">首页</router-link></h1> <h1><router-link to="/about">关于</router-link></h1> <h1><router-link to="/animal">动物</router-link></h1> </header> <main> <!--顶层路由出口--> <router-view></router-view> </main> </template>
2、About.vue
<!--普通组件--> <template> <h2>这里是关于页About.vue</h2> </template>
3、Animal.vue(二层母版)
<template> <h2>这里是关于动物页Animal.vue</h2> <header> <h1><router-link to="/panda-detail">熊猫详情</router-link></h1> <h1><router-link to="/dog-detail">狗狗详情</router-link></h1> </header> <main> <!--第二层路由出口--> <router-view></router-view> </main> </template>
4、PandaDetail.vue和DogDetail.vue
<!--普通组件--> <template> <h2>这里是熊猫详情页PandaDetail.vue</h2> </template> <!--普通组件--> <template> <h2>这里是狗狗详情页DogDetail.vue</h2> </template>
5、Router/index.js(路由管理器),嵌套设置主要在路由管理器中进行
//路由使用了name属性,用于指定路由的别名 //导航时即可以使用路径path: <RouterLink to="/about">关于</RouterLink> //也可以使用别名name: <RouterLink :to="{name:'about'}">关于</RouterLink> //RouterLink和router-link一样 import { createRouter, createWebHistory } from 'vue-router' const routes = [ { path: '/', name: 'home', component: ()=> import('../views/Index.vue'), }, { path: '/about', name: 'about', component: () => import('../views/About.vue') }, { path: '/animal', name: 'animal', component: () => import('../views/Animal.vue'), //Animal的子路由,以下路由的组件,在Animal组件的路由出口的切换显示 children:[ { path:'/panda-detail', name:'panda-detail', component: () => import('../views/PandaDetail.vue') }, { path:'/dog-detail', name:'dog-detail', component:() => import('../views/DogDetail.vue') } ] } ] const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes }) export default router
四、总结:通过嵌套路由实现,应该可以明显的感受到Blazor和Vue的路由和布局差异:
- Blazor是一个向上的过程,通过指定母版页,来确定母子嵌套关系。而Vue是一个向下的过程,通过指定子页面,来确定母子嵌套关系。
- Blazor的布局构件,包括了@page、@layout、@inherits LayoutComponentBase、@body。而Vue,就一个router-view,然后就是在路由配置文件(Router/index.js)中进行配置。它们的区别和路由一样,Blazor分散到各个组件中,而Vue集中到路由配置文件中。