[{"data":1,"prerenderedAt":1758},["ShallowReactive",2],{"note:code\u002Fvue\u002F07-router":3},{"id":4,"title":5,"body":6,"description":16,"extension":1752,"meta":1753,"navigation":136,"path":1754,"seo":1755,"stem":1756,"__hash__":1757},"notes\u002Fsource\u002Fcode\u002Fvue\u002F07-router.md","07 Vue Router 路由",{"type":7,"value":8,"toc":1727},"minimark",[9,13,17,20,25,28,39,42,45,51,58,64,68,71,256,259,334,337,381,386,394,441,463,466,472,480,527,530,555,565,569,575,607,610,614,617,623,626,668,674,677,795,798,857,861,864,870,873,887,890,945,948,1000,1003,1007,1014,1079,1082,1121,1127,1131,1134,1176,1179,1183,1186,1240,1251,1254,1262,1266,1269,1385,1388,1450,1453,1457,1460,1463,1479,1482,1485,1499,1502,1513,1517,1523,1530,1543,1547,1550,1575,1578,1603,1609,1613,1615,1629,1631,1644,1647,1656,1660,1669,1672,1678,1681,1684,1723],[10,11,5],"h1",{"id":12},"_07-vue-router-路由",[14,15,16],"p",{},"Vue Router 负责“当前 URL 对应哪个页面组件”。没有 Router，Vue 也能写组件；有了 Router，才方便做首页、列表页、详情页、设置页等多页面结构的单页应用。",[14,18,19],{},"本章目标：你能创建路由、使用链接跳转、读取动态参数、处理查询参数、写 404 页面，并理解 Router 和普通组件状态的边界。",[21,22,24],"h2",{"id":23},"_1-什么是单页应用里的路由","1. 什么是单页应用里的路由",[14,26,27],{},"传统多页网站：",[29,30,36],"pre",{"className":31,"code":33,"language":34,"meta":35},[32],"language-text","\u002Findex.html\n\u002Fabout.html\n\u002Fproducts.html\n","text","",[37,38,33],"code",{"__ignoreMap":35},[14,40,41],{},"点击链接时，浏览器向服务器请求一个新的 HTML 页面。",[14,43,44],{},"Vue 单页应用：",[29,46,49],{"className":47,"code":48,"language":34,"meta":35},[32],"\u002F\n\u002Flessons\n\u002Flessons\u002Fcore\n\u002Fsettings\n",[37,50,48],{"__ignoreMap":35},[14,52,53,54,57],{},"浏览器通常只加载一个 ",[37,55,56],{},"index.html","。URL 变化后，Vue Router 根据路径决定显示哪个组件。",[29,59,62],{"className":60,"code":61,"language":34,"meta":35},[32],"URL 变化 -> Router 匹配 routes -> 渲染对应 view 组件\n",[37,63,61],{"__ignoreMap":35},[21,65,67],{"id":66},"_2-安装和注册-router","2. 安装和注册 Router",[14,69,70],{},"创建路由：",[29,72,76],{"className":73,"code":74,"language":75,"meta":35,"style":35},"language-js shiki shiki-themes github-light github-dark","\u002F\u002F src\u002Frouter\u002Findex.js\nimport { createRouter, createWebHistory } from 'vue-router'\nimport HomeView from '..\u002Fviews\u002FHomeView.vue'\nimport AboutView from '..\u002Fviews\u002FAboutView.vue'\n\nconst router = createRouter({\n  history: createWebHistory(import.meta.env.BASE_URL),\n  routes: [\n    { path: '\u002F', name: 'home', component: HomeView },\n    { path: '\u002Fabout', name: 'about', component: AboutView }\n  ]\n})\n\nexport default router\n","js",[37,77,78,87,105,118,131,138,158,187,193,211,227,233,239,244],{"__ignoreMap":35},[79,80,83],"span",{"class":81,"line":82},"line",1,[79,84,86],{"class":85},"sJ8bj","\u002F\u002F src\u002Frouter\u002Findex.js\n",[79,88,90,94,98,101],{"class":81,"line":89},2,[79,91,93],{"class":92},"szBVR","import",[79,95,97],{"class":96},"sVt8B"," { createRouter, createWebHistory } ",[79,99,100],{"class":92},"from",[79,102,104],{"class":103},"sZZnC"," 'vue-router'\n",[79,106,108,110,113,115],{"class":81,"line":107},3,[79,109,93],{"class":92},[79,111,112],{"class":96}," HomeView ",[79,114,100],{"class":92},[79,116,117],{"class":103}," '..\u002Fviews\u002FHomeView.vue'\n",[79,119,121,123,126,128],{"class":81,"line":120},4,[79,122,93],{"class":92},[79,124,125],{"class":96}," AboutView ",[79,127,100],{"class":92},[79,129,130],{"class":103}," '..\u002Fviews\u002FAboutView.vue'\n",[79,132,134],{"class":81,"line":133},5,[79,135,137],{"emptyLinePlaceholder":136},true,"\n",[79,139,141,144,148,151,155],{"class":81,"line":140},6,[79,142,143],{"class":92},"const",[79,145,147],{"class":146},"sj4cs"," router",[79,149,150],{"class":92}," =",[79,152,154],{"class":153},"sScJk"," createRouter",[79,156,157],{"class":96},"({\n",[79,159,161,164,167,170,172,175,178,181,184],{"class":81,"line":160},7,[79,162,163],{"class":96},"  history: ",[79,165,166],{"class":153},"createWebHistory",[79,168,169],{"class":96},"(",[79,171,93],{"class":92},[79,173,174],{"class":96},".",[79,176,177],{"class":146},"meta",[79,179,180],{"class":96},".env.",[79,182,183],{"class":146},"BASE_URL",[79,185,186],{"class":96},"),\n",[79,188,190],{"class":81,"line":189},8,[79,191,192],{"class":96},"  routes: [\n",[79,194,196,199,202,205,208],{"class":81,"line":195},9,[79,197,198],{"class":96},"    { path: ",[79,200,201],{"class":103},"'\u002F'",[79,203,204],{"class":96},", name: ",[79,206,207],{"class":103},"'home'",[79,209,210],{"class":96},", component: HomeView },\n",[79,212,214,216,219,221,224],{"class":81,"line":213},10,[79,215,198],{"class":96},[79,217,218],{"class":103},"'\u002Fabout'",[79,220,204],{"class":96},[79,222,223],{"class":103},"'about'",[79,225,226],{"class":96},", component: AboutView }\n",[79,228,230],{"class":81,"line":229},11,[79,231,232],{"class":96},"  ]\n",[79,234,236],{"class":81,"line":235},12,[79,237,238],{"class":96},"})\n",[79,240,242],{"class":81,"line":241},13,[79,243,137],{"emptyLinePlaceholder":136},[79,245,247,250,253],{"class":81,"line":246},14,[79,248,249],{"class":92},"export",[79,251,252],{"class":92}," default",[79,254,255],{"class":96}," router\n",[14,257,258],{},"注册到应用：",[29,260,262],{"className":73,"code":261,"language":75,"meta":35,"style":35},"\u002F\u002F src\u002Fmain.js\nimport { createApp } from 'vue'\nimport App from '.\u002FApp.vue'\nimport router from '.\u002Frouter'\n\ncreateApp(App).use(router).mount('#app')\n",[37,263,264,269,281,293,305,309],{"__ignoreMap":35},[79,265,266],{"class":81,"line":82},[79,267,268],{"class":85},"\u002F\u002F src\u002Fmain.js\n",[79,270,271,273,276,278],{"class":81,"line":89},[79,272,93],{"class":92},[79,274,275],{"class":96}," { createApp } ",[79,277,100],{"class":92},[79,279,280],{"class":103}," 'vue'\n",[79,282,283,285,288,290],{"class":81,"line":107},[79,284,93],{"class":92},[79,286,287],{"class":96}," App ",[79,289,100],{"class":92},[79,291,292],{"class":103}," '.\u002FApp.vue'\n",[79,294,295,297,300,302],{"class":81,"line":120},[79,296,93],{"class":92},[79,298,299],{"class":96}," router ",[79,301,100],{"class":92},[79,303,304],{"class":103}," '.\u002Frouter'\n",[79,306,307],{"class":81,"line":133},[79,308,137],{"emptyLinePlaceholder":136},[79,310,311,314,317,320,323,326,328,331],{"class":81,"line":140},[79,312,313],{"class":153},"createApp",[79,315,316],{"class":96},"(App).",[79,318,319],{"class":153},"use",[79,321,322],{"class":96},"(router).",[79,324,325],{"class":153},"mount",[79,327,169],{"class":96},[79,329,330],{"class":103},"'#app'",[79,332,333],{"class":96},")\n",[14,335,336],{},"根组件放出口：",[29,338,342],{"className":339,"code":340,"language":341,"meta":35,"style":35},"language-vue shiki shiki-themes github-light github-dark","\u003C!-- App.vue -->\n\u003Ctemplate>\n  \u003CRouterView \u002F>\n\u003C\u002Ftemplate>\n","vue",[37,343,344,349,361,372],{"__ignoreMap":35},[79,345,346],{"class":81,"line":82},[79,347,348],{"class":85},"\u003C!-- App.vue -->\n",[79,350,351,354,358],{"class":81,"line":89},[79,352,353],{"class":96},"\u003C",[79,355,357],{"class":356},"s9eBZ","template",[79,359,360],{"class":96},">\n",[79,362,363,366,369],{"class":81,"line":107},[79,364,365],{"class":96},"  \u003C",[79,367,368],{"class":356},"RouterView",[79,370,371],{"class":96}," \u002F>\n",[79,373,374,377,379],{"class":81,"line":120},[79,375,376],{"class":96},"\u003C\u002F",[79,378,357],{"class":356},[79,380,360],{"class":96},[14,382,383,385],{},[37,384,368],{}," 是一个占位区域。当前路径匹配到哪个组件，哪个组件就显示在这里。",[21,387,389,390,393],{"id":388},"_3-routes-每个字段什么意思","3. ",[37,391,392],{},"routes"," 每个字段什么意思",[29,395,397],{"className":73,"code":396,"language":75,"meta":35,"style":35},"{\n  path: '\u002Fabout',\n  name: 'about',\n  component: AboutView\n}\n",[37,398,399,404,417,428,436],{"__ignoreMap":35},[79,400,401],{"class":81,"line":82},[79,402,403],{"class":96},"{\n",[79,405,406,409,412,414],{"class":81,"line":89},[79,407,408],{"class":153},"  path",[79,410,411],{"class":96},": ",[79,413,218],{"class":103},[79,415,416],{"class":96},",\n",[79,418,419,422,424,426],{"class":81,"line":107},[79,420,421],{"class":153},"  name",[79,423,411],{"class":96},[79,425,223],{"class":103},[79,427,416],{"class":96},[79,429,430,433],{"class":81,"line":120},[79,431,432],{"class":153},"  component",[79,434,435],{"class":96},": AboutView\n",[79,437,438],{"class":81,"line":133},[79,439,440],{"class":96},"}\n",[442,443,444,451,457],"ul",{},[445,446,447,450],"li",{},[37,448,449],{},"path","：URL 路径。",[445,452,453,456],{},[37,454,455],{},"name","：路由名字，方便编程式跳转。",[445,458,459,462],{},[37,460,461],{},"component","：这个路径要渲染的组件。",[14,464,465],{},"你可以理解为：",[29,467,470],{"className":468,"code":469,"language":34,"meta":35},[32],"当 URL 是 \u002Fabout 时，把 AboutView 放进 RouterView。\n",[37,471,469],{"__ignoreMap":35},[21,473,475,476,479],{"id":474},"_4-routerlink-跳转","4. ",[37,477,478],{},"RouterLink"," 跳转",[29,481,483],{"className":339,"code":482,"language":341,"meta":35,"style":35},"\u003CRouterLink to=\"\u002F\">首页\u003C\u002FRouterLink>\n\u003CRouterLink to=\"\u002Fabout\">关于\u003C\u002FRouterLink>\n",[37,484,485,507],{"__ignoreMap":35},[79,486,487,489,491,494,497,500,503,505],{"class":81,"line":82},[79,488,353],{"class":96},[79,490,478],{"class":356},[79,492,493],{"class":153}," to",[79,495,496],{"class":96},"=",[79,498,499],{"class":103},"\"\u002F\"",[79,501,502],{"class":96},">首页\u003C\u002F",[79,504,478],{"class":356},[79,506,360],{"class":96},[79,508,509,511,513,515,517,520,523,525],{"class":81,"line":89},[79,510,353],{"class":96},[79,512,478],{"class":356},[79,514,493],{"class":153},[79,516,496],{"class":96},[79,518,519],{"class":103},"\"\u002Fabout\"",[79,521,522],{"class":96},">关于\u003C\u002F",[79,524,478],{"class":356},[79,526,360],{"class":96},[14,528,529],{},"不要在应用内部优先用：",[29,531,533],{"className":339,"code":532,"language":341,"meta":35,"style":35},"\u003Ca href=\"\u002Fabout\">关于\u003C\u002Fa>\n",[37,534,535],{"__ignoreMap":35},[79,536,537,539,542,545,547,549,551,553],{"class":81,"line":82},[79,538,353],{"class":96},[79,540,541],{"class":356},"a",[79,543,544],{"class":153}," href",[79,546,496],{"class":96},[79,548,519],{"class":103},[79,550,522],{"class":96},[79,552,541],{"class":356},[79,554,360],{"class":96},[14,556,557,558,561,562,564],{},"普通 ",[37,559,560],{},"\u003Ca>"," 可能触发整页刷新。",[37,563,478],{}," 会交给 Vue Router 处理，保持单页应用体验。",[566,567,568],"h3",{"id":568},"激活样式",[14,570,571,572,574],{},"当前链接匹配时，",[37,573,478],{}," 会自动添加类名：",[29,576,580],{"className":577,"code":578,"language":579,"meta":35,"style":35},"language-css shiki shiki-themes github-light github-dark",".router-link-active {\n  color: #2563eb;\n}\n","css",[37,581,582,590,603],{"__ignoreMap":35},[79,583,584,587],{"class":81,"line":82},[79,585,586],{"class":153},".router-link-active",[79,588,589],{"class":96}," {\n",[79,591,592,595,597,600],{"class":81,"line":89},[79,593,594],{"class":146},"  color",[79,596,411],{"class":96},[79,598,599],{"class":146},"#2563eb",[79,601,602],{"class":96},";\n",[79,604,605],{"class":81,"line":107},[79,606,440],{"class":96},[14,608,609],{},"也可以配置自定义类名。",[21,611,613],{"id":612},"_5-动态路由","5. 动态路由",[14,615,616],{},"课程详情页通常有多个：",[29,618,621],{"className":619,"code":620,"language":34,"meta":35},[32],"\u002Flessons\u002Fjs\n\u002Flessons\u002Fcore\n\u002Flessons\u002Frouter\n",[37,622,620],{"__ignoreMap":35},[14,624,625],{},"不可能给每个课程都写一条路由。用动态参数：",[29,627,629],{"className":73,"code":628,"language":75,"meta":35,"style":35},"{\n  path: '\u002Flessons\u002F:id',\n  name: 'lesson-detail',\n  component: LessonDetailView\n}\n",[37,630,631,635,646,657,664],{"__ignoreMap":35},[79,632,633],{"class":81,"line":82},[79,634,403],{"class":96},[79,636,637,639,641,644],{"class":81,"line":89},[79,638,408],{"class":153},[79,640,411],{"class":96},[79,642,643],{"class":103},"'\u002Flessons\u002F:id'",[79,645,416],{"class":96},[79,647,648,650,652,655],{"class":81,"line":107},[79,649,421],{"class":153},[79,651,411],{"class":96},[79,653,654],{"class":103},"'lesson-detail'",[79,656,416],{"class":96},[79,658,659,661],{"class":81,"line":120},[79,660,432],{"class":153},[79,662,663],{"class":96},": LessonDetailView\n",[79,665,666],{"class":81,"line":133},[79,667,440],{"class":96},[14,669,670,673],{},[37,671,672],{},":id"," 表示这一段是变量。",[14,675,676],{},"读取参数：",[29,678,680],{"className":73,"code":679,"language":75,"meta":35,"style":35},"import { computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { lessons } from '..\u002Fdata\u002Flessons'\n\nconst route = useRoute()\n\nconst lesson = computed(() => {\n  return lessons.find((item) => item.id === route.params.id)\n})\n",[37,681,682,693,704,716,720,735,739,759,791],{"__ignoreMap":35},[79,683,684,686,689,691],{"class":81,"line":82},[79,685,93],{"class":92},[79,687,688],{"class":96}," { computed } ",[79,690,100],{"class":92},[79,692,280],{"class":103},[79,694,695,697,700,702],{"class":81,"line":89},[79,696,93],{"class":92},[79,698,699],{"class":96}," { useRoute } ",[79,701,100],{"class":92},[79,703,104],{"class":103},[79,705,706,708,711,713],{"class":81,"line":107},[79,707,93],{"class":92},[79,709,710],{"class":96}," { lessons } ",[79,712,100],{"class":92},[79,714,715],{"class":103}," '..\u002Fdata\u002Flessons'\n",[79,717,718],{"class":81,"line":120},[79,719,137],{"emptyLinePlaceholder":136},[79,721,722,724,727,729,732],{"class":81,"line":133},[79,723,143],{"class":92},[79,725,726],{"class":146}," route",[79,728,150],{"class":92},[79,730,731],{"class":153}," useRoute",[79,733,734],{"class":96},"()\n",[79,736,737],{"class":81,"line":140},[79,738,137],{"emptyLinePlaceholder":136},[79,740,741,743,746,748,751,754,757],{"class":81,"line":160},[79,742,143],{"class":92},[79,744,745],{"class":146}," lesson",[79,747,150],{"class":92},[79,749,750],{"class":153}," computed",[79,752,753],{"class":96},"(() ",[79,755,756],{"class":92},"=>",[79,758,589],{"class":96},[79,760,761,764,767,770,773,777,780,782,785,788],{"class":81,"line":189},[79,762,763],{"class":92},"  return",[79,765,766],{"class":96}," lessons.",[79,768,769],{"class":153},"find",[79,771,772],{"class":96},"((",[79,774,776],{"class":775},"s4XuR","item",[79,778,779],{"class":96},") ",[79,781,756],{"class":92},[79,783,784],{"class":96}," item.id ",[79,786,787],{"class":92},"===",[79,789,790],{"class":96}," route.params.id)\n",[79,792,793],{"class":81,"line":195},[79,794,238],{"class":96},[14,796,797],{},"模板：",[29,799,801],{"className":339,"code":800,"language":341,"meta":35,"style":35},"\u003Cp v-if=\"!lesson\">课程不存在\u003C\u002Fp>\n\u003Carticle v-else>\n  \u003Ch1>{{ lesson.title }}\u003C\u002Fh1>\n\u003C\u002Farticle>\n",[37,802,803,832,844,849],{"__ignoreMap":35},[79,804,805,807,809,812,814,817,820,823,825,828,830],{"class":81,"line":82},[79,806,353],{"class":96},[79,808,14],{"class":356},[79,810,811],{"class":92}," v-if",[79,813,496],{"class":96},[79,815,816],{"class":103},"\"",[79,818,819],{"class":92},"!",[79,821,822],{"class":96},"lesson",[79,824,816],{"class":103},[79,826,827],{"class":96},">课程不存在\u003C\u002F",[79,829,14],{"class":356},[79,831,360],{"class":96},[79,833,834,836,839,842],{"class":81,"line":89},[79,835,353],{"class":96},[79,837,838],{"class":356},"article",[79,840,841],{"class":92}," v-else",[79,843,360],{"class":96},[79,845,846],{"class":81,"line":107},[79,847,848],{"class":96},"  \u003Ch1>{{ lesson.title }}\u003C\u002Fh1>\n",[79,850,851,853,855],{"class":81,"line":120},[79,852,376],{"class":96},[79,854,838],{"class":356},[79,856,360],{"class":96},[21,858,860],{"id":859},"_6-查询参数","6. 查询参数",[14,862,863],{},"查询参数长这样：",[29,865,868],{"className":866,"code":867,"language":34,"meta":35},[32],"\u002Flessons?keyword=vue&page=2\n",[37,869,867],{"__ignoreMap":35},[14,871,872],{},"它适合表达：",[442,874,875,878,881,884],{},[445,876,877],{},"搜索词。",[445,879,880],{},"筛选条件。",[445,882,883],{},"分页页码。",[445,885,886],{},"排序方式。",[14,888,889],{},"读取：",[29,891,893],{"className":73,"code":892,"language":75,"meta":35,"style":35},"const route = useRoute()\n\nconst keyword = computed(() => {\n  return route.query.keyword ?? ''\n})\n",[37,894,895,907,911,928,941],{"__ignoreMap":35},[79,896,897,899,901,903,905],{"class":81,"line":82},[79,898,143],{"class":92},[79,900,726],{"class":146},[79,902,150],{"class":92},[79,904,731],{"class":153},[79,906,734],{"class":96},[79,908,909],{"class":81,"line":89},[79,910,137],{"emptyLinePlaceholder":136},[79,912,913,915,918,920,922,924,926],{"class":81,"line":107},[79,914,143],{"class":92},[79,916,917],{"class":146}," keyword",[79,919,150],{"class":92},[79,921,750],{"class":153},[79,923,753],{"class":96},[79,925,756],{"class":92},[79,927,589],{"class":96},[79,929,930,932,935,938],{"class":81,"line":120},[79,931,763],{"class":92},[79,933,934],{"class":96}," route.query.keyword ",[79,936,937],{"class":92},"??",[79,939,940],{"class":103}," ''\n",[79,942,943],{"class":81,"line":133},[79,944,238],{"class":96},[14,946,947],{},"跳转时带查询参数：",[29,949,951],{"className":73,"code":950,"language":75,"meta":35,"style":35},"router.push({\n  name: 'lessons',\n  query: {\n    keyword: keyword.value,\n    page: 1\n  }\n})\n",[37,952,953,963,973,978,983,991,996],{"__ignoreMap":35},[79,954,955,958,961],{"class":81,"line":82},[79,956,957],{"class":96},"router.",[79,959,960],{"class":153},"push",[79,962,157],{"class":96},[79,964,965,968,971],{"class":81,"line":89},[79,966,967],{"class":96},"  name: ",[79,969,970],{"class":103},"'lessons'",[79,972,416],{"class":96},[79,974,975],{"class":81,"line":107},[79,976,977],{"class":96},"  query: {\n",[79,979,980],{"class":81,"line":120},[79,981,982],{"class":96},"    keyword: keyword.value,\n",[79,984,985,988],{"class":81,"line":133},[79,986,987],{"class":96},"    page: ",[79,989,990],{"class":146},"1\n",[79,992,993],{"class":81,"line":140},[79,994,995],{"class":96},"  }\n",[79,997,998],{"class":81,"line":160},[79,999,238],{"class":96},[14,1001,1002],{},"查询参数的好处：用户可以复制 URL，把当前筛选状态分享给别人。",[21,1004,1006],{"id":1005},"_7-编程式导航","7. 编程式导航",[14,1008,1009,1010,1013],{},"除了 ",[37,1011,1012],{},"\u003CRouterLink>","，你也可以在 JS 中跳转。",[29,1015,1017],{"className":73,"code":1016,"language":75,"meta":35,"style":35},"import { useRouter } from 'vue-router'\n\nconst router = useRouter()\n\nfunction goHome() {\n  router.push('\u002F')\n}\n",[37,1018,1019,1030,1034,1047,1051,1062,1075],{"__ignoreMap":35},[79,1020,1021,1023,1026,1028],{"class":81,"line":82},[79,1022,93],{"class":92},[79,1024,1025],{"class":96}," { useRouter } ",[79,1027,100],{"class":92},[79,1029,104],{"class":103},[79,1031,1032],{"class":81,"line":89},[79,1033,137],{"emptyLinePlaceholder":136},[79,1035,1036,1038,1040,1042,1045],{"class":81,"line":107},[79,1037,143],{"class":92},[79,1039,147],{"class":146},[79,1041,150],{"class":92},[79,1043,1044],{"class":153}," useRouter",[79,1046,734],{"class":96},[79,1048,1049],{"class":81,"line":120},[79,1050,137],{"emptyLinePlaceholder":136},[79,1052,1053,1056,1059],{"class":81,"line":133},[79,1054,1055],{"class":92},"function",[79,1057,1058],{"class":153}," goHome",[79,1060,1061],{"class":96},"() {\n",[79,1063,1064,1067,1069,1071,1073],{"class":81,"line":140},[79,1065,1066],{"class":96},"  router.",[79,1068,960],{"class":153},[79,1070,169],{"class":96},[79,1072,201],{"class":103},[79,1074,333],{"class":96},[79,1076,1077],{"class":81,"line":160},[79,1078,440],{"class":96},[14,1080,1081],{},"按路由名字跳转：",[29,1083,1085],{"className":73,"code":1084,"language":75,"meta":35,"style":35},"router.push({\n  name: 'lesson-detail',\n  params: {\n    id: lesson.id\n  }\n})\n",[37,1086,1087,1095,1103,1108,1113,1117],{"__ignoreMap":35},[79,1088,1089,1091,1093],{"class":81,"line":82},[79,1090,957],{"class":96},[79,1092,960],{"class":153},[79,1094,157],{"class":96},[79,1096,1097,1099,1101],{"class":81,"line":89},[79,1098,967],{"class":96},[79,1100,654],{"class":103},[79,1102,416],{"class":96},[79,1104,1105],{"class":81,"line":107},[79,1106,1107],{"class":96},"  params: {\n",[79,1109,1110],{"class":81,"line":120},[79,1111,1112],{"class":96},"    id: lesson.id\n",[79,1114,1115],{"class":81,"line":133},[79,1116,995],{"class":96},[79,1118,1119],{"class":81,"line":140},[79,1120,238],{"class":96},[14,1122,1123,1124,1126],{},"推荐在复杂跳转中使用 ",[37,1125,455],{},"，因为路径以后改了，名字可以不变。",[21,1128,1130],{"id":1129},"_8-404-页面","8. 404 页面",[14,1132,1133],{},"用户访问不存在的路径时，应该显示 404 页面。",[29,1135,1137],{"className":73,"code":1136,"language":75,"meta":35,"style":35},"{\n  path: '\u002F:pathMatch(.*)*',\n  name: 'not-found',\n  component: NotFoundView\n}\n",[37,1138,1139,1143,1154,1165,1172],{"__ignoreMap":35},[79,1140,1141],{"class":81,"line":82},[79,1142,403],{"class":96},[79,1144,1145,1147,1149,1152],{"class":81,"line":89},[79,1146,408],{"class":153},[79,1148,411],{"class":96},[79,1150,1151],{"class":103},"'\u002F:pathMatch(.*)*'",[79,1153,416],{"class":96},[79,1155,1156,1158,1160,1163],{"class":81,"line":107},[79,1157,421],{"class":153},[79,1159,411],{"class":96},[79,1161,1162],{"class":103},"'not-found'",[79,1164,416],{"class":96},[79,1166,1167,1169],{"class":81,"line":120},[79,1168,432],{"class":153},[79,1170,1171],{"class":96},": NotFoundView\n",[79,1173,1174],{"class":81,"line":133},[79,1175,440],{"class":96},[14,1177,1178],{},"这条路由应该放在最后。因为它会匹配所有前面没匹配到的路径。",[21,1180,1182],{"id":1181},"_9-路由懒加载","9. 路由懒加载",[14,1184,1185],{},"页面很多时，不必一开始加载所有页面组件。",[29,1187,1189],{"className":73,"code":1188,"language":75,"meta":35,"style":35},"{\n  path: '\u002Fsettings',\n  name: 'settings',\n  component: () => import('..\u002Fviews\u002FSettingsView.vue')\n}\n",[37,1190,1191,1195,1206,1217,1236],{"__ignoreMap":35},[79,1192,1193],{"class":81,"line":82},[79,1194,403],{"class":96},[79,1196,1197,1199,1201,1204],{"class":81,"line":89},[79,1198,408],{"class":153},[79,1200,411],{"class":96},[79,1202,1203],{"class":103},"'\u002Fsettings'",[79,1205,416],{"class":96},[79,1207,1208,1210,1212,1215],{"class":81,"line":107},[79,1209,421],{"class":153},[79,1211,411],{"class":96},[79,1213,1214],{"class":103},"'settings'",[79,1216,416],{"class":96},[79,1218,1219,1221,1224,1226,1229,1231,1234],{"class":81,"line":120},[79,1220,432],{"class":153},[79,1222,1223],{"class":96},": () ",[79,1225,756],{"class":92},[79,1227,1228],{"class":92}," import",[79,1230,169],{"class":96},[79,1232,1233],{"class":103},"'..\u002Fviews\u002FSettingsView.vue'",[79,1235,333],{"class":96},[79,1237,1238],{"class":81,"line":133},[79,1239,440],{"class":96},[14,1241,1242,1243,1246,1247,1250],{},"这表示访问 ",[37,1244,1245],{},"\u002Fsettings"," 时才加载 ",[37,1248,1249],{},"SettingsView.vue","。",[14,1252,1253],{},"好处：",[442,1255,1256,1259],{},[445,1257,1258],{},"减少首屏加载体积。",[445,1260,1261],{},"用户不访问的页面不急着下载。",[21,1263,1265],{"id":1264},"_10-路由守卫","10. 路由守卫",[14,1267,1268],{},"如果某些页面需要登录后才能访问：",[29,1270,1272],{"className":73,"code":1271,"language":75,"meta":35,"style":35},"router.beforeEach((to) => {\n  const isLoggedIn = Boolean(localStorage.getItem('token'))\n\n  if (to.meta.requiresAuth && !isLoggedIn) {\n    return {\n      name: 'login',\n      query: {\n        redirect: to.fullPath\n      }\n    }\n  }\n})\n",[37,1273,1274,1292,1319,1323,1340,1347,1357,1362,1367,1372,1377,1381],{"__ignoreMap":35},[79,1275,1276,1278,1281,1283,1286,1288,1290],{"class":81,"line":82},[79,1277,957],{"class":96},[79,1279,1280],{"class":153},"beforeEach",[79,1282,772],{"class":96},[79,1284,1285],{"class":775},"to",[79,1287,779],{"class":96},[79,1289,756],{"class":92},[79,1291,589],{"class":96},[79,1293,1294,1297,1300,1302,1305,1308,1311,1313,1316],{"class":81,"line":89},[79,1295,1296],{"class":92},"  const",[79,1298,1299],{"class":146}," isLoggedIn",[79,1301,150],{"class":92},[79,1303,1304],{"class":153}," Boolean",[79,1306,1307],{"class":96},"(localStorage.",[79,1309,1310],{"class":153},"getItem",[79,1312,169],{"class":96},[79,1314,1315],{"class":103},"'token'",[79,1317,1318],{"class":96},"))\n",[79,1320,1321],{"class":81,"line":107},[79,1322,137],{"emptyLinePlaceholder":136},[79,1324,1325,1328,1331,1334,1337],{"class":81,"line":120},[79,1326,1327],{"class":92},"  if",[79,1329,1330],{"class":96}," (to.meta.requiresAuth ",[79,1332,1333],{"class":92},"&&",[79,1335,1336],{"class":92}," !",[79,1338,1339],{"class":96},"isLoggedIn) {\n",[79,1341,1342,1345],{"class":81,"line":133},[79,1343,1344],{"class":92},"    return",[79,1346,589],{"class":96},[79,1348,1349,1352,1355],{"class":81,"line":140},[79,1350,1351],{"class":96},"      name: ",[79,1353,1354],{"class":103},"'login'",[79,1356,416],{"class":96},[79,1358,1359],{"class":81,"line":160},[79,1360,1361],{"class":96},"      query: {\n",[79,1363,1364],{"class":81,"line":189},[79,1365,1366],{"class":96},"        redirect: to.fullPath\n",[79,1368,1369],{"class":81,"line":195},[79,1370,1371],{"class":96},"      }\n",[79,1373,1374],{"class":81,"line":213},[79,1375,1376],{"class":96},"    }\n",[79,1378,1379],{"class":81,"line":229},[79,1380,995],{"class":96},[79,1382,1383],{"class":81,"line":235},[79,1384,238],{"class":96},[14,1386,1387],{},"路由配置：",[29,1389,1391],{"className":73,"code":1390,"language":75,"meta":35,"style":35},"{\n  path: '\u002Fsettings',\n  name: 'settings',\n  component: SettingsView,\n  meta: {\n    requiresAuth: true\n  }\n}\n",[37,1392,1393,1397,1407,1417,1424,1432,1442,1446],{"__ignoreMap":35},[79,1394,1395],{"class":81,"line":82},[79,1396,403],{"class":96},[79,1398,1399,1401,1403,1405],{"class":81,"line":89},[79,1400,408],{"class":153},[79,1402,411],{"class":96},[79,1404,1203],{"class":103},[79,1406,416],{"class":96},[79,1408,1409,1411,1413,1415],{"class":81,"line":107},[79,1410,421],{"class":153},[79,1412,411],{"class":96},[79,1414,1214],{"class":103},[79,1416,416],{"class":96},[79,1418,1419,1421],{"class":81,"line":120},[79,1420,432],{"class":153},[79,1422,1423],{"class":96},": SettingsView,\n",[79,1425,1426,1429],{"class":81,"line":133},[79,1427,1428],{"class":153},"  meta",[79,1430,1431],{"class":96},": {\n",[79,1433,1434,1437,1439],{"class":81,"line":140},[79,1435,1436],{"class":153},"    requiresAuth",[79,1438,411],{"class":96},[79,1440,1441],{"class":146},"true\n",[79,1443,1444],{"class":81,"line":160},[79,1445,995],{"class":96},[79,1447,1448],{"class":81,"line":189},[79,1449,440],{"class":96},[14,1451,1452],{},"守卫适合判断“能不能进入页面”，不适合塞所有业务逻辑。",[21,1454,1456],{"id":1455},"_11-router-和-pinia-的边界","11. Router 和 Pinia 的边界",[14,1458,1459],{},"Router 管页面位置和 URL 状态。",[14,1461,1462],{},"适合放在 URL：",[442,1464,1465,1468,1471,1473,1476],{},[445,1466,1467],{},"当前页面。",[445,1469,1470],{},"详情 id。",[445,1472,877],{},[445,1474,1475],{},"分页。",[445,1477,1478],{},"排序。",[14,1480,1481],{},"Pinia 管业务状态。",[14,1483,1484],{},"适合放 Pinia：",[442,1486,1487,1490,1493,1496],{},[445,1488,1489],{},"当前登录用户。",[445,1491,1492],{},"购物车。",[445,1494,1495],{},"跨页面任务列表。",[445,1497,1498],{},"课程完成进度。",[14,1500,1501],{},"组件内状态：",[442,1503,1504,1507,1510],{},[445,1505,1506],{},"弹窗是否打开。",[445,1508,1509],{},"输入框正在输入但不需要分享的文字。",[445,1511,1512],{},"当前组件内部 tab。",[21,1514,1516],{"id":1515},"_12-常见错误","12. 常见错误",[566,1518,1520,1521],{"id":1519},"错误一忘记放-routerview","错误一：忘记放 ",[37,1522,368],{},[14,1524,1525,1526,1529],{},"配置了路由，但页面不显示，检查 ",[37,1527,1528],{},"App.vue"," 是否有：",[29,1531,1533],{"className":339,"code":1532,"language":341,"meta":35,"style":35},"\u003CRouterView \u002F>\n",[37,1534,1535],{"__ignoreMap":35},[79,1536,1537,1539,1541],{"class":81,"line":82},[79,1538,353],{"class":96},[79,1540,368],{"class":356},[79,1542,371],{"class":96},[566,1544,1546],{"id":1545},"错误二路由路径和链接不一致","错误二：路由路径和链接不一致",[14,1548,1549],{},"配置：",[29,1551,1553],{"className":73,"code":1552,"language":75,"meta":35,"style":35},"{ path: '\u002Flessons', component: LessonsView }\n",[37,1554,1555],{"__ignoreMap":35},[79,1556,1557,1560,1562,1564,1567,1570,1572],{"class":81,"line":82},[79,1558,1559],{"class":96},"{ ",[79,1561,449],{"class":153},[79,1563,411],{"class":96},[79,1565,1566],{"class":103},"'\u002Flessons'",[79,1568,1569],{"class":96},", ",[79,1571,461],{"class":153},[79,1573,1574],{"class":96},": LessonsView }\n",[14,1576,1577],{},"链接：",[29,1579,1581],{"className":339,"code":1580,"language":341,"meta":35,"style":35},"\u003CRouterLink to=\"\u002Flesson\">课程\u003C\u002FRouterLink>\n",[37,1582,1583],{"__ignoreMap":35},[79,1584,1585,1587,1589,1591,1593,1596,1599,1601],{"class":81,"line":82},[79,1586,353],{"class":96},[79,1588,478],{"class":356},[79,1590,493],{"class":153},[79,1592,496],{"class":96},[79,1594,1595],{"class":103},"\"\u002Flesson\"",[79,1597,1598],{"class":96},">课程\u003C\u002F",[79,1600,478],{"class":356},[79,1602,360],{"class":96},[14,1604,1605,1608],{},[37,1606,1607],{},"\u002Flesson"," 少了 s，会进入 404。",[566,1610,1612],{"id":1611},"错误三动态参数名字写错","错误三：动态参数名字写错",[14,1614,1549],{},[29,1616,1618],{"className":73,"code":1617,"language":75,"meta":35,"style":35},"path: '\u002Flessons\u002F:id'\n",[37,1619,1620],{"__ignoreMap":35},[79,1621,1622,1624,1626],{"class":81,"line":82},[79,1623,449],{"class":153},[79,1625,411],{"class":96},[79,1627,1628],{"class":103},"'\u002Flessons\u002F:id'\n",[14,1630,889],{},[29,1632,1634],{"className":73,"code":1633,"language":75,"meta":35,"style":35},"route.params.lessonId \u002F\u002F undefined\n",[37,1635,1636],{"__ignoreMap":35},[79,1637,1638,1641],{"class":81,"line":82},[79,1639,1640],{"class":96},"route.params.lessonId ",[79,1642,1643],{"class":85},"\u002F\u002F undefined\n",[14,1645,1646],{},"应该：",[29,1648,1650],{"className":73,"code":1649,"language":75,"meta":35,"style":35},"route.params.id\n",[37,1651,1652],{"__ignoreMap":35},[79,1653,1654],{"class":81,"line":82},[79,1655,1649],{"class":96},[566,1657,1659],{"id":1658},"错误四部署后刷新详情页-404","错误四：部署后刷新详情页 404",[14,1661,1662,1663,1666,1667,1250],{},"本地没问题，部署后访问 ",[37,1664,1665],{},"\u002Flessons\u002Fjs"," 刷新 404。原因是服务器不知道这个路径应该返回 ",[37,1668,56],{},[14,1670,1671],{},"SPA 部署需要配置：",[29,1673,1676],{"className":1674,"code":1675,"language":34,"meta":35},[32],"所有未知路径 -> \u002Findex.html\n",[37,1677,1675],{"__ignoreMap":35},[14,1679,1680],{},"这样刷新后先回到 Vue 应用，再由 Router 匹配页面。",[21,1682,1683],{"id":1683},"本章练习",[1685,1686,1687,1690,1697,1700,1706,1713,1720],"ol",{},[445,1688,1689],{},"创建首页、课程列表页、课程详情页、404 页。",[445,1691,1692,1693,1696],{},"配置 ",[37,1694,1695],{},"\u002Flessons\u002F:id"," 动态路由。",[445,1698,1699],{},"从课程列表点击进入详情页。",[445,1701,1702,1703,1250],{},"在详情页读取 ",[37,1704,1705],{},"route.params.id",[445,1707,1708,1709,1712],{},"给课程列表加 ",[37,1710,1711],{},"keyword"," 查询参数。",[445,1714,1715,1716,1719],{},"写一个按钮，用 ",[37,1717,1718],{},"router.push"," 跳转首页。",[445,1721,1722],{},"解释：为什么刷新动态路由可能在部署后 404？",[1724,1725,1726],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":35,"searchDepth":89,"depth":89,"links":1728},[1729,1730,1731,1733,1737,1738,1739,1740,1741,1742,1743,1744,1751],{"id":23,"depth":89,"text":24},{"id":66,"depth":89,"text":67},{"id":388,"depth":89,"text":1732},"3. routes 每个字段什么意思",{"id":474,"depth":89,"text":1734,"children":1735},"4. RouterLink 跳转",[1736],{"id":568,"depth":107,"text":568},{"id":612,"depth":89,"text":613},{"id":859,"depth":89,"text":860},{"id":1005,"depth":89,"text":1006},{"id":1129,"depth":89,"text":1130},{"id":1181,"depth":89,"text":1182},{"id":1264,"depth":89,"text":1265},{"id":1455,"depth":89,"text":1456},{"id":1515,"depth":89,"text":1516,"children":1745},[1746,1748,1749,1750],{"id":1519,"depth":107,"text":1747},"错误一：忘记放 RouterView",{"id":1545,"depth":107,"text":1546},{"id":1611,"depth":107,"text":1612},{"id":1658,"depth":107,"text":1659},{"id":1683,"depth":89,"text":1683},"md",{"order":160},"\u002Fsource\u002Fcode\u002Fvue\u002F07-router",{"title":5,"description":16},"source\u002Fcode\u002Fvue\u002F07-router","p1KiBx1agC3GzZQAYW7ShxdF1Weax7ZxBb394xOs8Ls",1784032889391]