# [ 想入門,我陪你 ] Re Vue 重頭說起|Day 17:巢狀路由與程式控制
###### tags: `Vue`、`Re:Vue 重頭說起`、`Alex 宅幹嘛`
## Nested Routes
通常指路由內的Content還有路由
```
/user/foo/profile /user/foo/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
```

```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 17</title>
<style>
.router-link-exact-active {
background-color: black;
color: white;
}
.page {
border: 5px solid red;
padding: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
min-height: 100px;
box-sizing: border-box;
}
</style>
</head>
<body>
<div id="app">
<p>
<router-link to="/">Index</router-link>
<router-link to="/page1">Page 1</router-link>
<router-link to="/user">User</router-link>
</p>
<router-view></router-view>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.9/"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.x.x/"></script>
<script>
const Index = { template: '<div>Index</div>' }
const Page1 = { template: '<div>Page1</div>' }
const User = {
template: '
<div>
User
<router-view></router-view>
</div>
'
}
const Profile = { template: '<div>Profile</div>' }
const Posts = { template: '<div>Posts</div>' }
const router = new VueRouter({
routes: [
{
path: '/',
component: Index,
},
{
path: '/page1',
component: Page1,
},
{
path: '/user',
component: User,
children: [
{
path: 'profile',
component: Profile,
},
{
path: 'posts',
component: Posts,
},
]
},
{
path: '*',
component: Error,
},
],
})
new Vue({
el: '#app',
router,
})
</script>
</body>
</html>
```

**Note that nested paths that start with / will be treated as a root path. This allows you to leverage the component nesting without having to use a nested URL.**
加斜線會變成根目錄開始計算,不是子目錄開始
```javascript=
const router = new VueRouter({
routes: [
{
path: '/',
component: Index,
},
{
path: '/page1',
component: Page1,
},
{
path: '/user',
component: User,
children: [
// 加上斜線
{
path: '/profile',
component: Profile,
},
{
path: '/posts',
component: Posts,
},
]
},
{
path: '*',
component: Error,
},
],
})
```


### Q&A 1
Q: (32:00) user/posts -> user/profiles,會不會console.log出 User
```javascript=
const User = {
template: '
<div>
User
<router-view></router-view>
</div>
',
mounted() {
console.log('User')
}
}
const Profile = { template: '<div>Profile</div>' }
const Posts = { template: '<div>Posts</div>' }
```
A: 不會,因為如果User模組已經出現在畫面上,他不會重新產生,就不會 mounted 到畫面
### Q&A 2 (34:40)
Q: / -> /posts
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 17</title>
<style>
.router-link-exact-active {
background-color: black;
color: white;
}
.page {
border: 5px solid red;
padding: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
min-height: 100px;
box-sizing: border-box;
}
</style>
</head>
<body>
<div id="app">
<p>
<router-link to="/">Index</router-link>
<router-link to="/Page1">Page1</router-link>
<router-link to="/posts">Posts</router-link>
<router-link to="/user">User</router-link>
<router-link to="/user/profile">User Profile</router-link>
<router-link to="/user/posts">User Posts</router-link>
</p>
<router-view></router-view>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.9/"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.x.x/"></script>
<script>
const Index = { template: '<div>Index</div>' }
const Page1 = { template: '<div>Page1</div>' }
const User = {
template: '
<div>
User
<router-view></router-view>
</div>
',
mounted() {
console.log('User')
}
}
const Profiles = {
template: '
<div>
Profiles
<router-view></router-view>
</div>
',
mounted() {
console.log('Profiles')
}
}
const Posts = {
template: '
<div>
Posts
<router-view></router-view>
</div>
',
mounted() {
console.log('Posts')
}
}
const router = new VueRouter({
routes: [
{
path: '/',
component: Index,
},
{
path: '/page1',
component: Page1,
},
{
path: '/user',
component: User,
children: [
{
path: 'profile',
component: Profile,
},
{
path: 'posts',
component: Posts,
},
{
path: '/posts',
component: Posts,
},
]
},
{
path: '*',
component: Error,
},
],
})
new Vue({
el: '#app',
router,
})
</script>
</body>
</html>
```
A: Posts -> User (componet是從內層一路 mounted 到外層)
Q2: /posts -> /user
A2: 只有移除Post模組,user沒動,所以沒console
Q3: posts 與 /posts 共用 Posts 模組,/user -> /user/posts
A3: console出 Posts,因為Posts進來,User保持不變
#### 幫 children 加上空字串的 path
當使用者進入 /user/foo 時,找不到匹配路由會指顯示User,裡面 <router-view></router-view> 會直接不存在
```javascript=
{
path: '/user',
component: User,
children: [
{
path: 'profile',
component: Profile,
},
{
path: 'posts',
component: Posts,
},
{
path: '/posts',
component: Posts,
},
]
},
```
所以可以補上空字串
```javascript=
const UserContent = {
template: '
<div>
UserContent
<router-view></router-view>
</div>
',
mounted() {
console.log('UserContent')
}
}
{
path: '/user',
component: User,
children: [
{
path: '',
component: UserContent,
},
{
path: 'profile',
component: Profile,
},
{
path: 'posts',
component: Posts,
},
{
path: '/posts',
component: Posts,
},
]
},
```
或是用上一章教的問號語法
```javascript=
path: '/user/(profile/)?:id(\\d+)',
```
注意: 以上路由因為被user用children包著,所以內部切換路由user模組不會重新mounted,但是vue-router若設計成平行路由如下,每次切換,user模組就會重新 mounted(48:28)
```javascript=
{
path: '/user',
component: User,
},
{
path: '/user/content',
component: UserContent,
},
```
> 順序規劃:資料 -> 路由 -> 模組
## Programmatic Navigation(50:30)
自己做 router-link ,用瀏覽器原生 router history 的概念
### router.push(location onComplete?, onAbort?)
- 以下是一樣的意思:
| Declarative | Programmatic |
| ----------- | ------------ |
| `<router-link :to="...">` | `router.push(...)` |
#### 用到 onComplete 時機:善後工作(57:59)
onComplete可以處理不能 rerender 的事
> These callbacks will be called when the navigation either successfully completed (after all async hooks are resolved)
```htmlmixed=
<div id="app">
<p>
<router-link to="/">Index</router-link>
<router-link to="/Page1">Page1</router-link>
<router-link to="/posts">Posts</router-link>
<router-link to="/user">User</router-link>
<router-link to="/user/profile">User Profile</router-link>
<router-link to="/user/posts">User Posts</router-link>
<button @click="clickHandler">User</button>
</p>
<router-view></router-view>
</div>
```
```javascript=
const Index = { template: '<div>Index</div>' }
const Page1 = { template: '<div>Page1</div>' }
const User = {
template: '
<div>
User
<router-view></router-view>
</div>
',
mounted() {
console.log('User')
}
}
const Profiles = {
template: '
<div>
Profiles
<router-view></router-view>
</div>
',
mounted() {
console.log('Profiles')
}
}
const Posts = {
template: '
<div>
Posts
<router-view></router-view>
</div>
',
mounted() {
console.log('Posts')
}
}
const router = new VueRouter({
routes: [
{
path: '/',
component: Index,
},
{
path: '/page1',
component: Page1,
},
{
path: '/user',
component: User,
children: [
{
path: 'profile',
component: Profile,
},
{
path: 'posts',
component: Posts,
},
{
path: '/posts',
component: Posts,
},
]
},
{
path: '*',
component: Error,
},
],
})
new Vue({
el: '#app',
router,
methods: {
clickHandler() {
this.$router.push('/user', completeHandler)
},
completeHandler() {
console.log('complete')
}
}
})
```
> 詳細敘述: 58:30
> 換頁時,有些模組需要 reset 時,可以用到 onComplete
> push is same rules apply for the to property of the router-link component.
```javascript=
const userId = '123'
router.push({ name: 'user', params: { userId } }) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// This will NOT work
router.push({ path: '/user', params: { userId } }) // -> /user
```
**In 3.1.0+, you can omit the 2nd and 3rd parameter and `router.push` / `router.replace` will return a promise instead if Promises are supported.**
1:05:29


- router with callback
`this.$router.push('/user', completeHandler)`
- router with promise
`this.$router.push('/user').then().catch()`
:::warning
**Note:** If the destination is the same as the current route and only params are changing (e.g. going from one profile to another `/users/1` -> `/users/2`), you will have to use **beforeRouteUpdate** to react to changes (e.g. fetching the user information).
如果路由 path 沒改變,但參數有改變,建議用 beforeRouteUpdate 去做響應
:::
### router.replace(location onComplete?, onAbort?)
- 以下是一樣的意思:
| Declarative | Programmatic |
| ----------- | ------------ |
| `<router-link :to="..." replace>` | `router.replace(...)` |
換頁不用產生歷史紀錄
特性如 push
#### Q & A 這三種情境的 Back 鍵是否會有作用
- 開新分頁,直接複製網址貼上進入
- 開新分頁,點擊我的最愛進入網站
- Google 收尋我的網站,點擊進入
A: Yes,但是設計Back按鈕會離開網站的功能真的很奇怪,所以不推
### router.go(n)
- 以下是一樣的意思:
| Declarative | Programmatic |
| ----------- | ------------ |
| `window.history.go(n)` | ` router.go(n)` |
### History Manipulation
| Declarative | Programmatic |
| ----------- | ------------ |
| `window.history.pushState` | `router.push` |
| `window.history.replaceState` | ` router.replace` |
| ` window.history.go` | ` router.go` |
> [Browser History APIs](https://developer.mozilla.org/en-US/docs/Web/API/History_API)
#### Q & A 導頁怎做
pushState 無法導頁,因為會變成 domain.com/www.google.com
此功能是換網址,但network沒有動靜
```javascript=
router.push("www.google.com")
window.history.pushState({}, "", "www.google.com")
```
成功導頁
```javascript=
window.location.href("www.google.com")
```
> Android WebView 不支援 window.location.href
> 解法:createElment a tag 然後用 js 製作 click 事件