# [ 想入門,我陪你 ] Re Vue 重頭說起|Day 17:巢狀路由與程式控制 ###### tags: `Vue`、`Re:Vue 重頭說起`、`Alex 宅幹嘛` ## Nested Routes 通常指路由內的Content還有路由 ``` /user/foo/profile /user/foo/posts +------------------+ +-----------------+ | User | | User | | +--------------+ | | +-------------+ | | | Profile | | +------------> | | Posts | | | | | | | | | | | +--------------+ | | +-------------+ | +------------------+ +-----------------+ ``` ![](https://i.imgur.com/KODcMkL.png) ```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> ``` ![](https://i.imgur.com/Md0BHHO.png) **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, }, ], }) ``` ![](https://i.imgur.com/rSlgxaz.png) ![](https://i.imgur.com/nTYMrt2.png) ### 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 ![](https://i.imgur.com/a1jpSfg.jpg) ![](https://i.imgur.com/2Tde9fi.png) - 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 事件