Vue Router
Introduction
Vue Router เป็น official Router จาก **Vue.js** ซึ่งมันสามารถใช้งานกับ Vue ได้อย่างมีประสิทธิภาพสูงสุด โดยเราสามารถทำทั้งหมดนี้ได้
- Nested routes mapping
- Dynamic Routing
- Modular, component-based router configuration
- Route params, query, wildcards
- View transition effects powered by Vue.js' transition system
- Fine-grained navigation control
- Links with automatic active CSS classes
- HTML5 history mode or hash mode
- Customizable Scroll Behavior
- Proper encoding for URLs
Installation
Package managers
ถ้า Project ของเราใช้ JavaScript package manager เราสามารถติดตั้ง Vue Router ได้จากคำสั่งต่อไปนี้
# npm
npm install vue-router@4
# yarn
yarn add vue-router@4
# pnpm
pnpm add vue-router@4
ถ้าคุณพึ่งเริ่มสร้างโปรเจค คุณสามารถใช้ **create-vue** ในการสร้าง Project ด้วย Vite ที่มี Vue Router ให้ใช้
#npm
npm create vue@latest
# yarn
yarn create vue
# pnpm
pnpm create vue
เราสามารถเรียกใช้ได้โดยเรียก import { createRouter } from 'vue-router'.
Direct Download / CDN
https://unpkg.com/vue-router@4
**Unpkg.com** ให้เราสามารถใช้ Vue Router จาก npm-Base ได้ Link ด้านบน จะบอกถึง Version ล่าสุดของ Vue Router หรือเราสามารถใช้ URLs แบบนี้ได้
https://unpkg.com/vue-router@4.0.15/dist/vue-router.global.js.
Getting Started
Vue Router ใช้กับฝั่ง client สำหรับ Vue
Client-Side Rounting ใช้โดย single-page applications (SPAs) ในการเปลี่ยน URL เพื่อแสดง Content ใหม่ ให้แก่ User. URL จะถูกเปลี่ยนแต่ว่าหน้า Page จะไม่ถูก Reload ใหม่
Vue Router สร้างบนระบบ Vue's component เราสามารถกำหนดได้ว่า Vue Router ได้ว่า routes เพื่อบอก Vue Router ได้ว่า components ไหนจะต้องแสดงที่ URL ไหน
An example
เราจะแสดงแนวคิดหลักของ Vue ต่อไปนี้ • Vue Playground example
App.vue
<template>
<h1>Hello App!</h1>
<p>
<strong>Current route path:</strong> {{ $route.fullPath }}
</p>
<nav>
<RouterLink to="/">Go to Home</RouterLink>
<RouterLink to="/about">Go to About</RouterLink>
</nav>
<main>
<RouterView />
</main>
</template>
template นี่ใช้ components 2 ตัว ที่มาจาก Vue Router RouterLink และRouterView.
แทนที่เราจะใช้ <a> tags ธรรมดา เราจะใช้ RouterLink ในการสร้างการเชื่อมต่อไปอีก URL โดยที่ไม่ทำให้เกิดการ Reload หน้า page ใหม่
RouterView component บอก Vue Router ที่จะ render route component. ที่เกี่ยวข้องกับ URL ที่เราต้องการให้แสดง
ในตัวอย่างด้านบนเราใช้ {{ $route.fullPath }}. เราสามารถใช้ $route ใน Component เราเพื่อเข้าถึง Object ใน route ปัจจุบันได้
Creating the router instance
router ถูกสร้าง โดยใช้ function createRouter()
import { createMemoryHistory, createRouter } from 'vue-router'
import HomeView from './HomeView.vue'
import AboutView from './AboutView.vue'
const routes = [
{ path: '/', component: HomeView },
{ path: '/about', component: AboutView },
]
const router = createRouter({
history: createMemoryHistory(),
routes,
})
routes option ใช้ประกาศ routes ที่เราต้องการ โดยเชื่อม URL เข้ากับ Component. Component จะถูกกำหนดอยู่ใน component option ที่จะถูก Render โดย <RouterView>
Routes จะมี Option ตัวอื่น ๆ ให้ใส่อีก แต่ในบทนี้ เราจะเน้นแค่ path และ component.
Registering the router plugin
เมื่อเราสร้าง router แล้ว เราต้อง register มันให้เป็น Plugin โดยใช้ use() บน Application ของเรา
createApp(App)
.use(router)
.mount('#app')
หรือ
const app = createApp(App)
app.use(router)
app.mount('#app')
ในการใช้ use() เราควรเรียกก่อนใช้ mount().
ถ้าคุณสงสัยเกี่ยวกับ Plugin นี้ ทำงานอย่างไร เราสามารถดูรายละเอียดเพิ่มเติมนี้ได้
- Globally registering ของ
RouterViewและRouterLinkcomponents. - เพิ่ม global
$routerและ$routeproperties. - เปิดการใช้งาน
useRouter()และuseRoute(). - Triggering router ในการ resolve initial route.
Accessing the router and current route
เราอาจจะต้องการเข้าถึง router จากทุกที่ใน Application ของเรา
ถ้าเราส่งออก router จาก ES module เราสามารถนำเข้า router จากที่ไหนก็ได้ที่เราต้องการใช้งาน ในบางกรณี นี่เป็นวิธีที่ดีที่สุด แต่เรามีทางเลือกอื่นให้ใช้ได้เหมือนกัน
ใน component templates. router จะถูกใช้งานได้จาก $router. นี่เหมือนกับ $route property ที่เราเห็นไปก่อนหน้านี้
ถ้าเราใช้ Options API, เราสามารถเข้าถึง Properties สองตัวนี้ได้ โดยการใช้ this.$router และ this.$route ใน JavaScript
HomeView.vue Component สามารถใช้ router ได้แบบนี้
export default {
methods: {
goToAbout() {
this.$router.push('/about')
},
},
}
method push() ที่ใช้กับ programmatic navigation. ที่เราจะเรียนเพิ่มเติมต่อไป
กับ Composition API, เราไม่จำเป็นต้องเข้าถึง route โดยใช้ this ดังนั้น Vue Router ให้เราสามารถใช้ composables ได้. AboutView.vue ใน Playground ก็ใช้วิธีนี้
<script setup>
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const router = useRouter()
const route = useRoute()
const search = computed({
get() {
return route.query.search ?? ''
},
set(search) {
router.replace({ query: { search } })
}
})
</script>
เราไม่จำเป็นต้องเข้าใจมันทั้งหมดก็ได้ สำคัญคือ useRouter() และ useRoute() ใช้เพื่อเข้าถึง router และ route ปัจจุบัน
Next steps
ถ้าเราต้องการที่จะสร้างโดยใช้ Vite เราสามารถใช้ **create-vue** ได้
#npm
npm create vue@latest
# yarn
yarn create vue
# pnpm
pnpm create vue
Conventions in this guide
Single-File Components
Vue Router จะแนะนำให้ใช้งานกับ Application Built (เช่น Vite) และ SFCs (i.e. .vue files). ตัวอย่างส่วนใหญ่ จะถูกเขียนในรูปแบบนั้น แต่ Vue Router ไม่ได้จำเป็นต้องเขียนแบบนี้เสมอไป
ตัวอย่างเช่น ถ้าเราใช้ global builds ของ Vue และ Vue Router, libraries สามารถใช้งานได้ผ่าน objects แทนที่จะเป็นการ Import
const { createApp } = Vue
const { createRouter, createWebHistory } = VueRouter
Component API style
Vue Router สามารถใช้ได้ทั้ง Composition API และ Options API เราจะแสดงการเขียนทั้ง 2 รูปแบบ ตัวอย่างของ Composition API จะเขียนโดยใช้ <script setup> มากกว่าการใช้ setup function
router and route
ตลอด Guide นี้ เราจะอ้างถึงอินสแตนซ์ของ router ว่าเป็น router ซึ่งหมายถึง Object ที่ได้จากการเรียก createRouter() วิธีการเข้าถึง Object นี้ใน Application ของคุณจะขึ้นอยู่กับบริบท ตัวอย่างเช่น ใน Component ที่ใช้ Composition API สามารถเข้าถึงได้โดยการเรียก useRouter() ส่วนใน Options API สามารถเข้าถึงได้โดยใช้ this.$router
ในทำนองเดียวกัน current route จะถูกเรียกว่า route ซึ่งสามารถเข้าถึงได้ใน Component โดยใช้ useRoute() หรือ this.$route ขึ้นอยู่กับว่าใช้ API แบบใดใน Component นั้น
RouterView and RouterLink
components RouterView และRouterLink พวกมันทั้งคู่เป็น registered globally, ดังนั้น เราไม่จำเป็นต้อง Import เข้ามาใน Component. อย่างไรก็ตาม, ถ้าเราต้องการ, เราสามารถ Import มันเข้ามาได้, ตัวอย่างเช่น import { RouterLink } from 'vue-router'.
Dynamic Route Matching with Params
หลายครั้ง ที่เราต้องการจะ map routes เข้ากับ Component เข้าด้วยกัน ตัวอย่างเช่น เราต้องการให้ User component render ให้สำหรับ User ทุกคน แต่มี ID ที่แตกต่างกัน ใน Vue Router เราสามารถใช้ dynamic path ได้ ในการเขียนแบบนั้น เราต้องเรียกการใช้งาน param
import User from './User.vue'
// these are passed to `createRouter`
const routes = [
// dynamic segments start with a colon
{ path: '/users/:id', component: User },
]
ตอนนี้ URLs เช่น /users/johnny และ /users/jolyne จะ map เป็น route เดียวกัน
param จะตามหลัง colon : เมื่อ route matched กันแล้ว ค่าของ params จะสามารถใช้ได้โดย route.params ในทุก Component และเราก็สามารถ render User ID โดยการเขียนแบบนี้
<template>
<div>
<!-- The current route is accessible as $route in the template -->
User {{ $route.params.id }}
</div>
</template>
เราสามารถมี params หลาย ๆ ตัวใน route เดียวได้ และ มันจะ map ตาม URL ที่เกี่ยวข้อง
| pattern | matched path | route.params |
|---|---|---|
| /users/:username | /users/eduardo | { username: 'eduardo' } |
| /users/:username/posts/:postId | /users/eduardo/posts/123 | { username: 'eduardo', postId: '123' } |
เพิ่มเติมของ route.params , ****route object ก็แสดงข้อมูลเพิ่มเติมได้เช่น route.query , route.hash, และ อื่น ๆ เราสามารถดูรายละเอียดเพิ่มเติมได้จาก API Reference.
Reacting to Params Changes
สิ่งหนึ่งที่เราควรจำไว้เมื่อเราใช้ routes เดียวกัน แต่ Params ต่างกัน เช่น /users/johnny ไปที่ /users/jolyne, component ตัวเดิมจะถูกใช้งานซ้ำ, เพราะว่าทั้งคู่ใช้ Component ตัวเดียวกัน ถ้าเราใช้ซ้ำมันจะมีประสิทธิภาพ มากกว่าการทำลาย Component ตัวเดิม แล้วสร้างใหม่ อย่างไรก็ตาม lifecycle hooks จะไม่ถูกเรียกใช้งาน
ในการทำงานเมื่อ params เปลี่ยนใน Component ตัวเดียวกัน เราสามารถใช้ watch กับ Params ได้
<script setup>
import { watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
watch(
() => route.params.id,
(newId, oldId) => {
// react to route changes...
}
)
</script>
หรือใช้ beforeRouteUpdate navigation guard, ที่อนุญาตให้เรายกเลิกการ navigation ได้
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
// ...
onBeforeRouteUpdate(async (to, from) => {
// react to route changes...
userData.value = await fetchUser(to.params.id)
})
</script>
Catch all / 404 Not found Route
params ปกติจะจับคู่เฉพาะ Character ที่อยู่ระหว่างส่วนของ URL ที่แยกด้วยเครื่องหมาย / หากเราต้องการจับคู่กับทุกอย่าง สามารถใช้ regexp (Regular Expression) แบบกำหนดเองได้โดยเพิ่ม regexp ไว้ในวงเล็บตามหลังพารามิเตอร์ทันที:
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.afterUser`
{ path: '/user-:afterUser(.*)', component: UserGeneric },
]
ในสถานการณ์นี้ เรากำลังใช้ custom regexp ภายในวงเล็บ และกำหนดให้ Params pathMatch เป็น **optionally repeatable.** วิธีนี้ช่วยให้เราสามารถ navigate ไปยัง path ดังกล่าวได้โดยตรง ถ้าเราต้องการแบ่ง path เป็น Array
router.push({
name: 'NotFound',
// preserve current path and remove the first char to avoid the target URL starting with `//`
params: { pathMatch: route.path.substring(1).split('/') },
// preserve existing query and hash if any
query: route.query,
hash: route.hash,
})
Advanced Matching Patterns
Vue Router สามารถเขียนแบบซับซ้อนขึ้นได้ เช่น optional params, zero or more / one or more requirements, และ custom regex patterns. สามารถตรวจสอบ **Advanced Matching** เพื่อเรียนรู้เพิ่มเติมได้
Routes' Matching Syntax
ใน Application ส่วนใหญ่ จะใช้ static routes เช่น /about และ dynamic routes เช่น /users/:userId
เหมือนที่เราเห็นใน Dynamic Route Matching, แต่ Vue Router สามารถทำได้มากกว่านั้น
Custom regex in params
เมื่อเราประกาศ param เช่น :userId, ภายในแล้ว เราใช้ regex ([^/]+) ในการแยก params จาก URLs นี่สามารถทำงานได้ดี ถ้าคุณจะใช้กับ route ที่ต่างกัน จิตนาการว่า เรามี routes สองตัว คือ /:orderId และ /:productName และถ้าเราจะใช้งานมัน เราต้องแยกออกมาเป็น 2 static routes ก่อน ค่อยใช้ dynamic param ภายใน
const routes = [
// matches /o/3549
{ path: '/o/:orderId' },
// matches /p/books
{ path: '/p/:productName' },
]
แต่ในบางกรณี เราไม่ต้องการที่จะเพิ่ม static routes อย่างไรก็ตาม orderId จะเป็นตัวเลขเสมอ ในขณะที่ productName สามารถเป็นอะไรก็ได้ ดังนั้น เราสามารถกำหนด regex สำหรับ param ได้
const routes = [
// /:orderId -> matches only numbers
{ path: '/:orderId(\\d+)' },
// /:productName -> matches anything else
{ path: '/:productName' },
]
ตอนนี้ /25 จะจับคู่กับ /:orderId ในขณะที่ตัวอื่น ๆ จะจับคู่กับ /:productName.
Repeatable params
ถ้าเราต้องการให้ routes จับคู่กับหลาย ๆ Path ได้ เช่น /first/second/third, เราสามารถกำหนดให้ param เป็น repeatable ด้วย * (0 หรือมากกว่า) และ + (1 หรือมากกว่า):
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 ของ params แทนที่จะเป็น String และเราจำเป็นต้องกำหนด Path โดยใช้ array ด้วย
// 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
เราสามารถรวมกับ custom regex โดยเพิ่มไปทีหลังจากเราปิดวงเล็บแล้ว
const routes = [
// only match numbers
// matches /1, /1/2, etc
{ path: '/:chapters(\\d+)+' },
// matches /, /1, /1/2, etc
{ path: '/:chapters(\\d+)*' },
]
Sensitive and strict route options
โดยปกติแล้ว routes ทุกตัวเป็น case-insensitive และ จับคู่ routes โดยมี หรือไม่มี slash ตามหลังก็ได้
เช่น route /users จับคู่กับ /users, /users/, หรือแม้แต่ /Users/. โดยพฤติกรรมพวกนี้ เราสามารถกำหนดได้ด้วย strict และ sensitive options, โดยเราสามารถกำหนดได้ตั้ง router และ route
const router = createRouter({
history: createWebHistory(),
routes: [
// will match /users/posva but not:
// - /users/posva/ because of strict: true
// - /Users/posva because of sensitive: true
{ path: '/users/:id', sensitive: true },
// will match /users, /Users, and /users/42 but not /users/ or /users/42/
{ path: '/users/:id?' },
],
strict: true, // applies to all routes
})
Optional parameters
เราสามารถทำให้ parameter เป็น optional ได้ โดยใช้ ? modifier ได้
const routes = [
// will match /users and /users/posva
{ path: '/users/:userId?' },
// will match /users and /users/42
{ path: '/users/:userId(\\d+)?' },
]
จำไว้ว่า * ก็สามารถทำให้ Parameter เป็น optional ได้ แต่ ? ไม่สามารถทำให้ parameters เป็น repeat ได้
ถ้าเราต้องการให้ route เก็บ Optional parameter มากกว่า 1 ตัว มันจะไม่จับคู่กับ ตัวที่ไม่มี slash ปิด เช่น
/users/:uid?-:name?จะไม่คู่กับ/users, จับคู่แค่/users/-หรือ/users/-//users/:uid(\\d+)?:name?จะไม่คู่กับ/users, แค่/users/,/users/2,/users/2/
Named Routes
เมื่อเราสร้าง route เราสามารถกำหนดชื่อของมันได้
const routes = [
{
path: '/user/:username',
name: 'profile',
component: User
}
]
เราสามารถกำหนดให้ไปที่ name แทน path ได้ เมื่อเราส่ง to prop ไปที่ <router-link>:
<router-link :to="{ name: 'profile', params: { username: 'erina' } }">
User profile
</router-link>
ในตัวอย่าง มันจะสร้างการเชื่อมต่อไปที่ /user/erina.
ข้อดีของการใช้ name
- เขียนโค้ดง่ายขึ้น
- เข้ารหัส
params - กันการเขียนผิด
name แต่ละตัว ต้องแตกต่างกัน เพราะว่า Vue จะสามารถแยก location ได้ เช่น การใช้ method router.push() และ router.replace() ก็สนับสนุนการใช้ name
router.push({ name: 'profile', params: { username: 'erina' } })
Nested Routes
application บางตัว UI จะถูกแสดงใน Component ที่ซ้อน ๆ กันลงไป ในกรณีนั้น โครงสร้างของ URL ควรจะเป็นแบบนี้
/user/johnny/profile /user/johnny/posts
┌──────────────────┐ ┌──────────────────┐
│ User │ │ User │
│ ┌──────────────┐ │ │ ┌──────────────┐ │
│ │ Profile │ │ ●────────────▶ │ │ Posts │ │
│ │ │ │ │ │ │ │
│ └──────────────┘ │ │ └──────────────┘ │
└──────────────────┘ └──────────────────┘
ด้วย Vue Router คุณสามารถแสดงให้เห็นถึงความสัมพันธ์ โดยใช้ nested route configurations.
<!-- App.vue -->
<template>
<router-view />
</template>
<!-- User.vue -->
<template>
<div>
User {{ $route.params.id }}
</div>
</template>
import User from './User.vue'
// these are passed to `createRouter`
const routes = [{ path: '/user/:id', component: User }]
<router-view> เป็น top-level router-view มัน renders component ที่จับคู่กับ top level route.
คล้ายกับ rendered component สามารถเก็บ nested <router-view> ของมันเองได้
ถ้าเราเพิ่มเข้าไปใน User Component template
<!-- User.vue -->
<template>
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view />
</div>
</template>
ในการ render components ใน nested router-view เราสามารถใช้ children option ได้ ใน route ไหน ๆ ก็ได้
const routes = [
{
path: '/user/:id',
component: User,
children: [
{
// UserProfile will be rendered inside User's <router-view>
// when /user/:id/profile is matched
path: 'profile',
component: UserProfile,
},
{
// UserPosts will be rendered inside User's <router-view>
// when /user/:id/posts is matched
path: 'posts',
component: UserPosts,
},
],
},
]
จำไว้ว่า nested paths ที่เริ่มด้วย / จะถูกปฏิบัติให้เป็น Root Path นี่อนุญาตให้คุณใช้งาน component nesting โดยไม่ต้องมี nested URL
อย่างที่เราเห็น children option เป็น Array ของ routes เหมือนกับ routes ดังนั้นเราสามารถ nesting ได้มากเท่าที่เราต้องการ
ณ จุดนี้ ด้วย configuration ด้านบน เมื่อเราเข้าถึง /user/eduardo จะไม่มีอะไร render ใน User's router-view เพราะว่าไม่มี nested route ตัวไหนจับคู่กัน บางทีเราอาจจะต้องการทำอะไรซักอย่างกับมัน ในกรณีนี้ เราสามารถใส่ empty nested path ได้
const routes = [
{
path: '/user/:id',
component: User,
children: [
// UserHome will be rendered inside User's <router-view>
// when /user/:id is matched
{ path: '', component: UserHome },
// ...other sub routes
],
},
]
เราสามารถดูตัวอย่างได้จากนี่ here.
Nested Named Routes
เมื่อเราทำงานกับ **Named Routes** เราสามารถตั้งชื่อของ Children route ได้
const routes = [
{
path: '/user/:id',
component: User,
// notice how only the child route has a name
children: [{ path: '', name: 'user', component: UserHome }],
},
]
นี่จะทำให้เรามั่นใจว่า เรา navigating ไปที่ /user/:id เสมอ
ในบางสถานการณ์ คุณอาจต้องการ Navigate ไปยัง named route โดยไม่ต้อง Navigate ไปยังเส้น nested route เช่น หากคุณต้องการ Navigate ไปยัง /user/:id โดยไม่แสดง nested route. ในกรณีนี้ คุณสามารถตั้งชื่อ parent route ได้เช่นกัน แต่ควรทราบว่า หากมีการโหลดหน้าใหม่ nested route จะถูกแสดงเสมอ เนื่องจากถือว่าเป็นการ Navigate ไปยังเส้นทาง /users/:id แทนที่จะเป็น route ที่ตั้งชื่อไว้โดยตรง:
const routes = [
{
path: '/user/:id',
name: 'user-parent',
component: User,
children: [{ path: '', name: 'user', component: UserHome }],
},
]
Omitting parent components (4.1+)
เรายังสามารถใช้ประโยชน์จากความสัมพันธ์ระหว่าง route แบบ Parent-child ได้ โดยไม่จำเป็นต้องซ้อน Component ของ route ได้ วิธีนี้ มีประโยชน์สำหรับการจัดกลุ่ม routes ด้วย ตัวนำหน้า (Prefix) หรือทำงานกับ features ที่ขั้นสูงขึ้นได้ เช่น per-route navigation guards หรือ route meta fields.
ในการทำแบบนี้ เราสามารถละเว้น component และ components option จาก parent route ได้
const routes = [
{
path: '/admin',
children: [
{ path: '', component: AdminOverview },
{ path: 'users', component: AdminUserList },
{ path: 'users/:id', component: AdminUserDetails },
],
},
]
เนื่องจาก Route Parent ไม่ได้ระบุ Component สำหรับ Route ไว้. <router-view> จะข้าม Parent ไป และใช้งาน child แทน
Programmatic Navigation
นอกจากการใช้ <router-link> ในการสร้าง Tag ในการเชื่อมไปยังหน้าอื่น เราสามารถใช้ router's instance methods ได้
Navigate to a different location
จำไว้ว่า ตัวอย่างด้านล่างอ้างอิงถึง router โดยใช้ router ได้. ใน component เราสามารถเข้าถึง $router property ได้**,** เช่น this.$router.push(...). ถ้าเราใช้ Composition API. router สามารถเข้าถึงได้โดยใช้ **useRouter().**
ในการ navigate ไปที่ URL ที่ต่างกัน โดยใช้ router.push . โดย method นี้เพิ่มข้อมูลเข้าที่ history, ดังนั้นเมื่อ user คลิ้กปุ่มย้อนกลับ มันจะกลับไปที่ URL ตัวเก่า
method นี้ จะถูกเรียกใช้ใน <router-link>, ดังนั้นถ้าเราใช้ <router-link :to="..."> จะเท่ากับการใช้ router.push(...).
| Declarative | Programmatic |
|---|---|
<router-link :to="..."> | router.push(...) |
argument สามารถ String path ได้
// 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' } })
// 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' })
โปรดจำไว้ว่า หากมีการ path ไว้ ระบบจะไม่สนใจ parameters, แต่ในกรณีที่ใช้ query คุณต้องระบุชื่อของ route หรือกำหนด route ทั้งหมดเองพร้อมกับ parameter ที่ต้องการ
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
เมื่อเรากำหนด params , เราต้องมั่นใจว่าเราใส่ string หรือ number เข้าไป หรือ ถ้าเป็น Type อื่น ๆ (objects, booleans, etc) จะถูกแปลงเป็น String โดยอัตโนมัติ. สำหรับ optional params, เราสามารถใช้ string ("") หรือ null เพื่อใช้ลบค่า
router.push และ navigation method ตัวอื่น ๆ จะ return promise และทำให้เราสามารถรอได้จนกว่าการ navigation จะจบ และ ถ้าเราต้องการจะรู้ว่า สามารถ navigate ได้ หรือไม่ได้ เราจะคุยกันในบท Navigation Handling.
Replace current location
มันทำหน้าที่คล้าย router.push, ความแตกต่างเดียวคือ มันจะไม่เพิ่มข้อมูลเข้าไปใน history, ตามชื่อของมัน - มันจะแทนที่ URL ปัจจุบัน
| Declarative | Programmatic |
|---|---|
<router-link :to="..." replace> | router.replace(...) |
เราสามารถเพิ่ม Property replace: true เข้าไปที่ router.push ได้ โดยมันจะแทนที่ path ทั้งหมด
router.push({ path: '/home', replace: true })
// equivalent to
router.replace({ path: '/home' })
Traverse history
method นี้ จะรับเลขจำนวนเต็มเข้ามา โดยที่จะกำหนดว่าจะให้ไปข้างหน้าหรือถอยหลังกี่รอบ ใน history stack คล้ายกับ window.history.go(n).
// 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)
History Manipulation
เราได้เรียนรู้การใช้ router.push, router.replace และ router.go นี่คล้ายกับ window.history.pushState, window.history.replaceStateและwindow.history.go, และเลียนแบบ window.history APIs.
ดังนั้น ถ้าคุณคุ้นเคยกับการใช้ Browser History APIs, ในการควบคุม history จะคล้ายกับการใช้ Vue Router
Named Views
บางครั้งเราต้องการที่จะแสดงหลาย ๆ views ในเวลาเดียวกัน เช่น สร้าง layout sidebar view ไว้ที่ข้าง ๆ และ main view ไว้ตรงกลาง และนี่เป็นสาเหตุของการใช้ name views แทนที่จะมีแค่ view แค่ตัวเดียว เราสามารถมี views หลาย ๆ ตัวได้ และ สามารถให้ name กับแต่ละตัวได้. router-view ที่ไม่มี name มันจะเป็นค่า default เป็น name ของมัน
<router-view class="view left-sidebar" name="LeftSidebar" />
<router-view class="view main-content" />
<router-view class="view right-sidebar" name="RightSidebar" />
view จะ render โดยใช้ component ดังนั้น views หลาย ๆ ตัว ต้องใช้หลาย ๆ Component. ทำให้มั่นใจว่าเราใช้ components
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
components: {
default: Home,
// short for LeftSidebar: LeftSidebar
LeftSidebar,
// they match the `name` attribute on `<router-view>`
RightSidebar,
},
},
],
})
ตัวอย่างการใช้งาน here.
Nested Named Views
มันเป็นไปได้เราสามารถสร้าง layouts ที่ซับซ้อนโดยใช้ named views กับ nested views. ได้. คุณจะต้องให้ชื่อ router-view กับมัน เรามาลองออกแบบสิ่งที่เราจะทำดูก่อน
/settings/emails /settings/profile
+-----------------------------------+ +------------------------------+
| UserSettings | | UserSettings |
| +-----+-------------------------+ | | +-----+--------------------+ |
| | Nav | UserEmailsSubscriptions | | +------------> | | Nav | UserProfile | |
| | +-------------------------+ | | | +--------------------+ |
| | | | | | | | UserProfilePreview | |
| +-----+-------------------------+ | | +-----+--------------------+ |
+-----------------------------------+ +------------------------------+
Navเป็น component ปกติUserSettingsเป็น parent view componentUserEmailsSubscriptions,UserProfile,UserProfilePreviewเป็น nested view components
<template> section สำหรับ UserSettings component ใน layout ด้านบนจะเหมือนกับโค้ดแบบนี้
<!-- UserSettings.vue -->
<div>
<h1>User Settings</h1>
<NavBar />
<router-view />
<router-view name="helper" />
</div>
ดังนั้นเราสามารถตั้ง configuration ได้แบบนั้น
{
path: '/settings',
// You could also have named views at the top
component: UserSettings,
children: [
{
path: 'emails',
component: UserEmailsSubscriptions
},
{
path: 'profile',
components: {
default: UserProfile,
helper: UserProfilePreview
}
}
]
}
Redirect and Alias
Redirect
Redirecting สามารถทำได้ใน routes configuration. ในการ redirect จาก /home ไปที่ /:
const routes = [{ path: '/home', redirect: '/' }]
ในการ redirect เราสามารถใช้ name route ได้
const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
หรือสามารถเรียกใช้เป็น function ได้
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',
// ...
},
]
จำไว้ว่า **Navigation Guards** ไม่สามารถใช้ได้กับ redirects. ในการใส่ beforeEnter เพื่อป้องกัน /home จะไม่สามารถใช้ได้
Relative redirecting
เราสามารถ redirect ไปที่ relative location ได้
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
redirect หมายความว่า เมื่อเราเข้าถึง /home, URL จะแทนที่ด้วย / และเปลี่ยนไปหน้า / แต่อะไรคือ alias?
alias ของ / เป็น /home หมายความว่า เมื่อ User เข้าชมหน้า /home, URL จะยังคงเป็น /home แต่จะจับคู่ path ราวกับว่าผู้ใช้กำลังเข้าชม /
const routes = [{ path: '/', component: Homepage, alias: '/home' }]
alias จะให้อิสระเราในการเชื่อม UI structure เข้ากับ URL แทนที่จะเราจะต้องทำโครงสร้างซ้อน ๆ กัน. ใส่ / เพื่อทำให้ path อยู่ภายใน nested routes. เราสามารถรวม aliases หลาย ๆ ตัวได้ โดยใช้ array
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'] },
],
},
]
ถ้า route มี parameters เราต้องเพิ่มมันเข้าไปที่ alias ด้วย
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
เมื่อเราใช้ $route หรือ useRoute() ใน Component ของเรา จะทำให้เกิดการ Link กัน ที่ไม่ยืดหยุ่นมากเท่าไหร่นัก เพราะว่ามันกำหนดแค่ให้ไปที่ URL นั้น ๆ เท่านั้น.
นี่ไม่ได้เป็นเรื่องแย่เสมอไป แต่เราสามารถแยกพฤติกรรมนี้ออกได้โดยใช้ตัวเลือก props เพื่อเพิ่มความยืดหยุ่นของ Component ได้
เราลองแสดงการทำงานกันดีกว่า
<!-- User.vue -->
<template>
<div>
User {{ $route.params.id }}
</div>
</template>
กับ
import User from './User.vue'
// these are passed to `createRouter`
const routes = [
{ path: '/users/:id', component: User },
]
เราสามารถลบ direct dependency บน $route ใน User.vue โดยประกาศ prop แทน
<!-- User.vue -->
<script setup>
defineProps({
id: String
})
</script>
<template>
<div>
User {{ id }}
</div>
</template>
เราสามารถตั้งค่า route ให้สามารถส่ง id param เข้าไปได้เป็น prop props: true:
const routes = [
{ path: '/user/:id', component: User, props: true }
]
นี่สามารถทำให้คุณสามารถใช้ component ได้จากทุกที่ โดยมันจะทำให้ Component ของเราสามารถใช้ซ้ำได้และ reuse ได้
Boolean mode
เมื่อ props ถูกตั้งเป็น true . route.params จะถูกตั้งค่าเป็น component props.
Named views
สำหรับ routes ที่มี named views เราจำเป็นต้องประกาศ props option สำหรับ name view แต่ละตัว
Object mode
เมื่อ props เป็น Object, ค่านั้นจะถูกตั้งค่าเป็น props ของ Component โดยตรง, จะมีประโยชน์เมื่อ props เป็น static
const routes = [
{
path: '/promotion/from-newsletter',
component: Promotion,
props: { newsletterPopup: false }
}
]
Function mode
เราสามารถสร้าง function ที่ return props. นี่จะสามารถให้เราแปลงประเภทของ parameters เป็น type อื่นได้, รวมกับ static values กับ route-based values, และอื่น ๆ ได้
const routes = [
{
path: '/search',
component: SearchUser,
props: route => ({ query: route.query.q })
}
]
URL /search?q=vue จะส่งค่า {query: 'vue'} มาเป็น prop ให้กับ SearchUser component.
Via RouterView
เราสามารถส่ง ptop ทาง **<RouterView> slot** ได้
<RouterView v-slot="{ Component }">
<component
:is="Component"
view-prop="value"
/>
</RouterView>
Active links
มันเป็นเรื่องปกติของ Application ที่จะมี navigate Component ที่จะ render list ของ RouterLink components. ด้วย list นั้น เราอาจจะต้องการที่จะ เราอาจต้องการตกแต่ง link ที่เชื่อมโยงกับ route ที่กำลังใช้งาน (active route) ให้แตกต่างจาก link อื่นๆ
RouterLink component เพิ่ม 2 CSS classes ไปที่ active link, router-link-active และ router-link-exact-active. ในการเข้าใจความแตกต่างของพวกมัน, เราต้องพิจารณาว่า Vue Router รู้ได้ไงว่า link active
When are links active?
RouterLink จะถูกพิจารณาว่า active เมื่อ:
- เมื่อมันมี route record เดียวกัน (เช่น configured route) เป็น location เดียวกัน
- เมื่อมันมีค่า
paramsตัวเดียวกัน เมื่ออยู่ location เดียวกัน
ถ้าเราใช้ nested routes, ถ้าเรามี link ที่ไป ancestor routes จะถูกพิจารณาว่าเป็น active link ถ้า parameter ที่ตรงกัน
oute properties ตัวอื่น เช่น **query** จะไม่ถูกนำมาพิจารณา
path ไม่จำเป็นต้องตรงกันโดยสมบูรณ์ เช่นเราใช้ **alias** มันก็ยังจะถูกพิจารณาให้คู่ตรงกันอยู่, ตราบใดที่มันมี route record เดียวกัน และ params เหมือนกัน
ถ้า route มี **redirect** มันจะไม่ถูก check ว่าเป็น active link
Exact active links
exact match จะไม่รวมกับ ancestor routes
จินตนาการว่า เรามี routes แบบนี้
const routes = [
{
path: '/user/:username',
component: User,
children: [
{
path: 'role/:roleId',
component: Role,
},
],
},
]
นี่จะถูกพิจารณาเป็น 2 links
<RouterLink to="/user/erina">
User
</RouterLink>
<RouterLink to="/user/erina/role/admin">
Role
</RouterLink>
ถ้า location ปัจจุบันมี path เป็น /user/erina/role/admin ดังนั้นมันทั้งคู่จะถูกพิจารณาเป็น active ดังนั้น class router-link-active จะถูกรวมเข้ากับ link ทั้ง 2 ตัว แต่แค่ link ตัวที่ 2 จะถูกพิจารณาให้เป็น exact, ดังนั้น link ตัวที่สอง จะมี class router-link-exact-active.
Configuring the classes
RouterLink component มี props 2 ตัว, activeClass และ exactActiveClass ,ที่จะใช้เปลี่ยนชื่อของ class ได้
<RouterLink
activeClass="border-indigo-500"
exactActiveClass="border-indigo-700"
...
>
ชื่อ class สามารถเปลี่ยนได้ โดยส่ง linkActiveClass และ linkExactActiveClass options ไปที่ createRouter()
const router = createRouter({
linkActiveClass: 'border-indigo-500',
linkExactActiveClass: 'border-indigo-700',
// ...
})
สามารถตรวจสอบ Extending RouterLink สำหรับเทคนิคขั้นสูงได้
Different History modes
history option เมื่อเราสร้าง router มันอนุญาตให้เราเลือกใช้ history modes ที่ต่างกันได้
Hash Mode
hash history mode สร้างด้วย createWebHashHistory()
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [
//...
],
})
มันใช้ hash character (#) ก่อน URL เพราะว่า ส่วนนี้ของ URL จะไม่ถูกส่งไปยังเซิร์ฟเวอร์, มันจึงไม่จำเป็นต้องได้รับการปฏิบัติที่พิเศษที่ระดับเซิร์ฟเวอร์ แต่การใช้เครื่องหมาย hash มีผลกระทบที่ไม่ดีต่อ SEO, ใช้ HTML5 history mode.
HTML5 Mode
HTML5 mode ถูกสร้างโดยใช้ createWebHistory() , เป็น mode ที่เราแนะนำ
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
//...
],
})
เมื่อเราใช้ createWebHistory() , URL จะดูปกติ เช่น https://example.com/user/id.
โดยตอนนี้จะเกิดปัญหาขึ้น: เมื่อ Application ของเราเป็น single page client side, โดยที่ไม่มีการตั้ง config ที่เหมาะสมจาก server, user จะได้รับ Error 404
แต่ไม่ต้องห่วง ในการแก้ไขปัญหานี้, ที่เราต้องทำคือเพิ่ม catch-all fallback route ไปที่ server, ถ้า URL ไม่ตรงกับค่าที่เราตั้งไว้ มันควรจะแสดงผลหน้า index.html ที่ Application ของเรา
Memory mode
History mode แบบ Memory จะไม่สมมติการใช้ใน environment ของ browser, ดังนั้นจึงไม่ทำงานกับ URL และจะไม่ navigation โดยอัตโนมัติ ซึ่งทำให้มันเหมาะสมกับสภาพแวดล้อม Node และการทำ (SSR), mode นี้จะถูกสร้างขึ้นด้วย createMemoryHistory() และคุณต้องประกาศใช้ app.use(router) ก่อนจะมีการ navigate
import { createRouter, createMemoryHistory } from 'vue-router'
const router = createRouter({
history: createMemoryHistory(),
routes: [
//...
],
})
ถึงแม้ว่าจะไม่แนะนำให้ใช้โหมดนี้ในแอปพลิเคชันของเบราว์เซอร์ แต่คุณสามารถใช้มันได้ โดยต้องระวังว่าจะไม่มีประวัติการเข้าชม (history) หมายความว่าคุณจะไม่สามารถย้อนกลับหรือไปข้างหน้าในประวัติการใช้งานได้
Example Server Configurations
หมายเหตุ: ตัวอย่างต่อไปนี้สมมติว่าคุณกำลังให้บริการแอปจากโฟลเดอร์ราก (root folder) หากคุณนำไปใช้งานในโฟลเดอร์ย่อย (subfolder) คุณควรใช้ตัวเลือก publicPath ของ Vue CLI และ base property. ที่เกี่ยวข้องของ router นอกจากนี้คุณยังต้องปรับตัวอย่างด้านล่างให้ใช้โฟลเดอร์ย่อยแทนโฟลเดอร์ราก (เช่น การแทนที่ RewriteBase / ด้วย RewriteBase /name-of-your-subfolder/)
Apache
<IfModule mod_negotiation.c>
Options -MultiViews
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
แทนที่จะใช้mod_rewrite, เราสามารถใช้ FallbackResource.
nginx
location / {
try_files $uri $uri/ /index.html;
}
Native Node.js
const http = require('http')
const fs = require('fs')
const httpPort = 80
http
.createServer((req, res) => {
fs.readFile('index.html', 'utf-8', (err, content) => {
if (err) {
console.log('We cannot open "index.html" file.')
}
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8',
})
res.end(content)
})
})
.listen(httpPort, () => {
console.log('Server listening on: http://localhost:%s', httpPort)
})
Express with Node.js
สำหรับ Node.js/Express พิจารณาการใช้ connect-history-api-fallback middleware.
Internet Information Services (IIS)
- ติดตั้ง IIS UrlRewrite
- สร้าง
web.configfile ใน root directory ของ folder ที่เราทำงาน และ เขียนตามนี้:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Handle History Mode and custom 404/500" stopProcessing="true">
<match url="(.*)" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
Caddy v2
try_files {path} /
Caddy v1
rewrite {
regexp .*
to {path} /
}
Firebase hosting
เพิ่มโค้ดนี้เข้าไปที่ firebase.json
{
"hosting": {
"public": "dist",
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}
Netlify
สร้าง _redirects file ที่รวมกับโค้ดนี้
/* /index.html 200
ใน vue-cli, nuxt, และ vite projects, file นี้จะรวมอยู่ใน static หรือ public.
Vercel
สร้าง vercel.json file ใต้ root directory ของ project เรา ตามนี้
{
"rewrites": [{ "source": "/:path*", "destination": "/index.html" }]
}
Caveat
มีข้อควรระวังในเรื่องนี้: เซิร์ฟเวอร์ของคุณจะไม่แสดง Error 404 อีกต่อไป เนื่องจาก Page ที่ไม่พบทั้งหมดจะถูกส่งไปยังไฟล์ index.html ของคุณแทน เพื่อหลีกเลี่ยงปัญหานี้ คุณควร implement เส้นทาง catch-all ภายในแอป Vue ของคุณเพื่อแสดงหน้า Error 404
const router = createRouter({
history: createWebHistory(),
routes: [{ path: '/:pathMatch(.*)', component: NotFoundComponent }],
})
อีกทางเลือกหนึ่ง หากคุณกำลังใช้เซิร์ฟเวอร์ Node.js คุณสามารถ implement การ fallback โดยการใช้ router บนฝั่งเซิร์ฟเวอร์เพื่อตรวจสอบ URL ที่เข้ามา และตอบกลับด้วย Error 404 หากไม่พบเส้นทางที่ตรงกัน
ตรวจสอบ Vue server side rendering documentation สำหรับข้อมูลเพิ่มเติม.