Blazor和Vue對比學習(進階.路由導航二):佈局(母版/巢狀)
單檔案元件框架中,當更改請求地址時,並不會引發頁面跳轉,而是由框架捕獲請求地址(在框架中我們稱之為路由),然後根據路由與元件的對映關係,在頁面的指定位置切換和顯示元件。在哪個位置顯示(稱之為路由出口),就是佈局要解決的問題。無論是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集中到路由配置檔案中。