以下為 Vue Router 的一個例子,Vue Router 的用途,是能夠在單一網頁頁面中,在 browser 不重新載入到新的 url 的狀況下,能夠增加 url history 並調整頁面內容狀態的功能,也就是能夠實現 SPA (single page application) 的功能。
傳統的網頁,會向 web application server 要求打開一個 url 網址,server 會回傳整個網頁的 html 內容。後來為了在不轉向到新的 url 的條件下,並調整網頁的內容,就發生了 web service,server 會從某個網址回傳 XML 或 JSON,網頁透過 AJAX 方式提取資料更新網頁內容。SPA 是更進一步,可在不重新發出新的 url request 到 server 的條件下,更新網頁內容並增加 url 瀏覽歷程,也就是增加了單一網頁頁面的顯示狀態。
Vue router 實例:
<!DOCTYPE html>
<html lang="en">
<head>
<!-- <script src="https://unpkg.com/vue@3.2.10"></script> -->
<!-- <script src="https://unpkg.com/vue@3.2.10/dist/vue.global.js"></script> -->
<script src="https://unpkg.com/vue@3.2.10/dist/vue.global.prod.js"></script>
<!-- <script src="https://unpkg.com/vue-router@4.0.11"></script> -->
<!-- <script src="https://unpkg.com/vue-router@4.0.11/dist/vue.global.js"></script> -->
<script src="https://unpkg.com/vue-router@4.0.11/dist/vue-router.global.prod.js"></script>
<!--
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> -->
</head>
<body>
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- use the router-link component for navigation. -->
<!-- specify the link by passing the `to` prop. -->
<!-- `<router-link>` will render an `<a>` tag with the correct `href` attribute -->
<router-link to="/">Go to Home</router-link>
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- route outlet -->
<!-- component matched by the route will render here -->
<router-view></router-view>
</div>
<script>
// 1. Define route components.
// These can be imported from other files
const Home = { template: '<div>Home</div>' }
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 2. Define some routes
// Each route should map to a component.
// We'll talk about nested routes later.
const routes = [
{ path: '/', component: Home },
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
// 3. Create the router instance and pass the `routes` option
// You can pass in additional options here, but let's
// keep it simple for now.
const router = VueRouter.createRouter({
// 4. Provide the history implementation to use. We are using the hash history for simplicity here.
history: VueRouter.createWebHashHistory(),
routes, // short for `routes: routes
})
// 5. Create and mount the root instance.
const app = Vue.createApp({})
// Make sure to _use_ the router instance to make the
// whole app router-aware.
app.use(router)
app.mount('#app')
// Now the app has started!
</script>
</body>
</html>
以下是點擊了 foo 以後,網頁的 DOM 產生的資料。 foo 的部分,會自動加上這兩個 css class class ="router-link-exact-active router-link-active"
<div id="app" data-v-app="">
<h1>Hello App!</h1>
<p>
<a href="#/" class="">Go to Home</a>
<a href="#/foo" class="router-link-active router-link-exact-active" aria-current="page">Go to Foo</a>
<a href="#/bar" class="">Go to Bar</a>
</p>
<div>foo</div>
</div>
<router-link>
相關屬性
to
<!-- 直接填寫文字字串 -->
<router-link to="/home">Home</router-link>
<!-- 結果 -->
<a href="/home">Home</a>
<!-- 使用 v-bind,省略 path -->
<router-link v-bind:to="'/home'">Home</router-link>
<!-- 不寫 v-bind 也可以,就像綁定別的屬性一樣 -->
<router-link :to="'/home'">Home</router-link>
<!-- 同上 -->
<router-link :to="{ path: '/home' }">Home</router-link>
<!-- 命名的路由 -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
<!-- 帶查詢參數,下面的結果為 /register?plan=private -->
<router-link :to="{ path: '/register', query: { plan: 'private' }}">Register</router-link>
replace
如果不希望在 browser 留下 url history,可加上 replace
<router-link to="/home" replace>Home</router-link>
當點擊時,會呼叫 router.replace() 而不是 router.push()
active-class
設定當 link 啟用時,DOM 節點使用的 css class
<style>
._active{
background-color : red;
}
</style>
<p>
<router-link v-bind:to = "{ path: '/route1'}" active-class = "_active">Router Link 1</router-link>
<router-link v-bind:to = "{ path: '/route2'}">Router Link 2</router-link>
</p>
aria-current-value
預設為 "page",可能的值
'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false' (string)
當 link 為 active 時,傳送給 aria-current
屬性的值
custom
預設為 "false"
決定 <router-link>
是不是"不要"產生到 <a>
裡面。如果使用 v-slot
產生 custom router link,預設要包裝在 <a>
裡面,如果增加 custom 屬性,就取消這個限制
<router-link to="/home" custom v-slot="{ navigate, href, route }">
<a :href="href" @click="navigate">{{ route.fullPath }}</a>
</router-link>
會 render 為
<a href="/home">/home</a>
<router-link to="/home" v-slot="{ route }">
<span>{{ route.fullPath }}</span>
</router-link>
會 render 為
<a href="/home"><span>/home</span></a>
exact-active-class
當 link 被精確匹配時,要啟用的 css class
<router-link>
的 v-slot
可產生自訂的 html tag,記得一定要加上 custom
ex:
<router-link
to="/foo"
custom
v-slot="{ href, route, navigate, isActive, isExactActive }"
>
<NavLink :active="isActive" :href="href" @click="navigate">
{{ route.fullPath }}
</NavLink>
</router-link>
- href: resolved url,類似 a tag 的 href
- route: resolved normalized location
- navigate: 驅動 navigation 的 function,必要時,會自動 prevent events
- isActive: 如果被 apply active class ,就會是 true
- isExactActive: 如果被 apply exact active class,就會是 true
會 render 為
<navlink active="true" href="#/foo">/foo</navlink>
ex:
<ul>
<router-link
to="/foo"
custom
v-slot="{ href, route, navigate, isActive, isExactActive }"
>
<li
:class="[isActive && 'router-link-active', isExactActive && 'router-link-exact-active']"
>
<a :href="href" @click="navigate">{{ route.fullPath }}</a>
</li>
</router-link>
</ul>
會 render 為
<ul>
<li class="router-link-active router-link-exact-active"><a href="#/foo">/foo</a></li>
</ul>
Dynamic Route Matching
Vue 需要將某個 pattern 對應到一個 component 的方法,例如不同 userid 的 user 資料
$route.params
可用來協助對應 route 的 pattern 上的參數
const User = {
template: '<div>User {{ $route.params.id }}</div>',
}
// these are passed to `createRouter`
const routes = [
// dynamic segments start with a colon
{ path: '/users/:id', component: User },
]
pattern | matching | $route.params |
---|---|---|
/users/:id | /users/john | { id: 'john' } |
/users/:id/posts/:postid | /users/john/posts/123 | { id: 'john', posted: '123' } |
Reacting to Params Changes
因為 /users/john
跟 /users/mary
這兩個路徑會使用同一個 component,比較有效率的方法,是不重建 component,直接更新內容,但這樣也不會呼叫到 component 的 lifecycle hooks
const User = {
template: '<div>User {{ $route.params.id }}</div>',
created() {
this.$watch(
() => this.$route.params,
(toParams, previousParams) => {
// react to route changes...
console.log("created toParams=", toParams, ", previousParams=",previousParams);
}
)
},
// navigation guard,可在這邊檢查並取消 navigation
async beforeRouteUpdate(to, from) {
// react to route changes...
// this.userData = await fetchUser(to.params.id)
console.log("beforeRouteUpdate to=", to, ", from=",from);
this.userData = to.params.id
},
}
Catch /404 Not Found Route
const routes = [
// will match everything and put it under `$route.params.pathMatch`
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
// will match anything starting with `/user-` and put it under `$route.params.id`
{ path: '/user-:id(.*)', component: User },
]
如果直接在 brwoser 網址連結到
1-2-param.html#/not
就會是用 NotFound 這個 Component 處理
如果直接在 brwoser 網址連結到
1-2-param.html#/user-test/12345
會收到 $route.params.id
的值為 test/12345
Routes' Matching Syntax
大部分的 application 使用 static route 以及類似 /users/:userId
這樣的 route
custom regex
如果要在 url 分辨 orderId 及 productName,最簡單的方法就是加上 /o
/p
靜態的部分用來區別
const routes = [
// matches /o/3549
{ path: '/o/:orderId' },
// matches /p/books
{ path: '/p/:productName' },
]
如果 orderId 跟 productName 可用數字/字串區分,可將 route 改為
const routes = [
// /:orderId -> matches only numbers
{ path: '/:orderId(\\d+)' },
// /:productName -> matches anything else
{ path: '/:productName' },
]
repeatable params
如果是 /first/second/third
這樣的 route
*
為 0 or more, +
為 1 or more
const routes = [
// /:chapters -> matches /one, /one/two, /one/two/three, etc
{ path: '/:chapters+' },
// /:chapters -> matches /, /one, /one/two, /one/two/three, etc
{ path: '/:chapters*' },
]
可用以下方式,將 array 參數轉換為 path
// given { path: '/:chapters*', name: 'chapters' },
router.resolve({ name: 'chapters', params: { chapters: [] } }).href
// produces /
router.resolve({ name: 'chapters', params: { chapters: ['a', 'b'] } }).href
// produces /a/b
// given { path: '/:chapters+', name: 'chapters' },
router.resolve({ name: 'chapters', params: { chapters: [] } }).href
// throws an Error because `chapters` is empty
const routes = [
// only match numbers
// matches /1, /1/2, etc
{ path: '/:chapters(\\d+)+' },
// matches /, /1, /1/2, etc
{ path: '/:chapters(\\d+)*' },
]
optional param
?
代表 0 or 1
const routes = [
// will match /users and /users/posva
{ path: '/users/:userId?' },
// will match /users and /users/42
{ path: '/users/:userId(\\d+)?' },
]
Nested Routes
在 application 裡面常見到 nested components
ex:
/user/johnny/profile /user/johnny/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
<div id="app">
<h1>Nested Views</h1>
<p>
<router-link to="/users/eduardo">/users/eduardo</router-link>
<br />
<router-link to="/users/eduardo/profile"
>/users/eduardo/profile</router-link
>
<br />
<router-link to="/users/eduardo/posts">/users/eduardo/posts</router-link>
</p>
<router-view></router-view>
</div>
<script>
const User = {
template: '<h2>User {{ $route.params.username }}</h2><router-view></router-view>',
}
const UserHome = {
template: '<div>Home</div>',
}
const UserProfile = {
template: '<div>UserProfile</div>',
}
const UserPosts = {
template: '<div>UserPosts</div>',
}
const routes = [
{
path: '/users/:username',
component: User,
children: [
// UserHome will be rendered inside User's <router-view>
// when /users/:username is matched
{ path: '', component: UserHome },
// UserProfile will be rendered inside User's <router-view>
// when /users/:username/profile is matched
{ path: 'profile', component: UserProfile },
// UserPosts will be rendered inside User's <router-view>
// when /users/:username/posts is matched
{ path: 'posts', component: UserPosts },
],
}
]
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes,
})
const app = Vue.createApp({})
app.use(router)
app.mount('#app')
</script>
Programmatic Navigation
除了用 <router-link>
產生 url anchor tags 以外,也可以用程式處理
navigate to a different location
在 vue application 中,可用 $router 為 route instance,故要呼叫 this.$router.push
<router-link :to="...">
就等同於 router.push(...)
// literal string path
router.push('/users/eduardo')
// object with path
router.push({ path: '/users/eduardo' })
// named route with params to let the router build the url
router.push({ name: 'user', params: { username: 'eduardo' } })
// 如果有 path,就會忽略 params
// with query, resulting in /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })
// with hash, resulting in /about#team
router.push({ path: '/about', hash: '#team' })
可用 uername 變數
const username = 'eduardo'
// we can manually build the url but we will have to handle the encoding ourselves
router.push(`/user/${username}`) // -> /user/eduardo
// same as
router.push({ path: `/user/${username}` }) // -> /user/eduardo
// if possible use `name` and `params` to benefit from automatic URL encoding
router.push({ name: 'user', params: { username } }) // -> /user/eduardo
// `params` cannot be used alongside `path`
router.push({ path: '/user', params: { username } }) // -> /user
replace current location
切換網址,但不會 push 到 history
<router-link :to="..." replace>
等同 router.replace(...)
router.push({ path: '/home', replace: true })
// equivalent to
router.replace({ path: '/home' })
history
// go forward by one record, the same as router.forward()
router.go(1)
// go back by one record, the same as router.back()
router.go(-1)
// go forward by 3 records
router.go(3)
// fails silently if there aren't that many records
router.go(-100)
router.go(100)
named route
為 route 命名,優點:
- 沒有 hardcoded url
- 可自動 encode/decode params
- 填寫 url 不會發生 typo
- bypass path ranking
const routes = [
{
path: '/user/:username',
name: 'user',
component: User
}
]
用以下方式使用 named route
<router-link :to="{ name: 'user', params: { username: 'erina' }}">
User
</router-link>
或
router.push({ name: 'user', params: { username: 'erina' } })
named views
有時候,需要一次顯示多個 views,但不是 nested view
/settings/emails /settings/profile
+-----------------------------------+ +------------------------------+
| UserSettings | | UserSettings |
| +-----+-------------------------+ | | +-----+--------------------+ |
| | Nav | UserEmailsSubscriptions | | +------------> | | Nav | UserProfile | |
| | +-------------------------+ | | | +--------------------+ |
| | | | | | | | UserProfilePreview | |
| +-----+-------------------------+ | | +-----+--------------------+ |
+-----------------------------------+ +------------------------------+
<div id="app">
<h1>Nested Named Views</h1>
<router-view></router-view>
</div>
<script>
const About = {
template: '<h1>About</h1>',
}
const Home = {
template: '<div>Home</div>',
}
const UserEmailsSubscriptions = {
template: '<div><h3>Email Subscriptions</h3></div>',
}
const UserProfile = {
template: '<div><h3>UserProfile</h3></div>',
}
const UserProfilePreview = {
template: '<div><h3>UserProfilePreview</h3></div>',
}
const UserSettingsNavTemplate = `
<div class="us__nav">
<router-link to="/settings/emails">emails</router-link>
<br />
<router-link to="/settings/profile">profile</router-link>
</div>
`
// const UserSettingsNav = {
// template: UserSettingsNavTemplate
// }
const UserSettingsTemplate = `
<div class="us">
<h2>User Settings</h2>
<user-settings-nav />
<router-view class="us__content" />
<router-view name="helper" class="us__content us__content--helper" />
</div>
`
const UserSettings = {
template: UserSettingsTemplate
}
const routes = [
{
path: '/settings',
// You could also have named views at tho top
component: UserSettings,
children: [
{
path: 'emails',
component: UserEmailsSubscriptions,
},
{
path: 'profile',
components: {
default: UserProfile,
helper: UserProfilePreview,
},
},
],
},
]
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes,
})
const app = Vue.createApp({})
app.use(router)
app.component('user-settings-nav', {
template: UserSettingsNavTemplate
})
app.mount('#app')
</script>
Redirect and Alias
直接在 routes 做 redirect
const routes = [{ path: '/home', redirect: '/' }]
const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
用 redirect function 做 dynamic redirect
const routes = [
{
// /search/screens -> /search?q=screens
path: '/search/:searchText',
redirect: to => {
// the function receives the target route as the argument
// we return a redirect path/location here.
return { path: '/search', query: { q: to.params.searchText } }
},
},
{
path: '/search',
// ...
},
]
可 redirect 到相對路徑
const routes = [
{
// will always redirect /users/123/posts to /users/123/profile
path: '/users/:id/posts',
redirect: to => {
// the function receives the target route as the argument
// a relative location doesn't start with `/`
// or { path: 'profile'}
return 'profile'
},
},
]
alias
將 /
alias 為 /home
,代表瀏覽 /home
或 /
都是一樣的 component
const routes = [{ path: '/', component: Homepage, alias: '/home' }]
在 nested view 也可以用
const routes = [
{
path: '/users',
component: UsersLayout,
children: [
// this will render the UserList for these 3 URLs
// - /users
// - /users/list
// - /people
{ path: '', component: UserList, alias: ['/people', 'list'] },
],
},
]
如果有參數
const routes = [
{
path: '/users/:id',
component: UsersByIdLayout,
children: [
// this will render the UserDetails for these 3 URLs
// - /users/24
// - /users/24/profile
// - /24
{ path: 'profile', component: UserDetails, alias: ['/:id', ''] },
],
},
]
Passing Props to Route Components
在 component 使用 $route
時,會跟 route 綁定在一起。可利用 props
option
可將以下語法
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const routes = [{ path: '/user/:id', component: User }]
替換為
const User = {
// make sure to add a prop named exactly like the route param
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const routes = [{ path: '/user/:id', component: User, props: true }]
Boolean mode
當 props
設定為 true
,就表示 route.params
會指定為 component props
Named views
可為每一個 named view 定義 props
const routes = [
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
Object mode
const routes = [
{
path: '/promotion/from-newsletter',
component: Promotion,
props: { newsletterPopup: false }
}
]
Function mode
const routes = [
{
path: '/search',
component: SearchUser,
props: route => ({ query: route.query.q })
}
]
URL /search?q=vue
會將 {query: 'vue'}
以 props 傳給 SearchUser
component
History Mode
Hash Mode
在網址後面加上 #
,不利於 search engine
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [
//...
],
})
HTML5 Mode
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
//...
],
})
在 url 會看到 https://example.com/user/id
,但實際上如果直接在 browser 對 server 發送 https://example.com/user/id
這個 request,會出現 404 Error
解決方式就是單純地將該網址用 index.html 服務