路由进阶
路由进阶
以下为学习过程中的极简提炼笔记,以供重温巩固学习
学习准备
准备工作
html+css+JavaScript 3剑客都懂一点,完成AJAX原理,熟悉AJAX函数与调用,熟悉AJAX前端接口处理
学习目的
- 路由进阶
- ① 路由模块封装
- ② 声明式导航 & 导航高亮 / 精确匹配&模糊匹配 / 自定义高亮类名 / 声明式导航传参 ( 查询参数传参 & 动态路由传参 )
- ③ 路由重定向 / 路由404 / 路由模式
- ④ 编程式导航 / 编程式导航传参 ( 查询参数传参 & 动态路由传参 )
- 综合案例:面经基础版
- 一级路由 / 二级路由 / 导航高亮 / 请求渲染 / 路由传参 / 缓存组件
路由进阶
路由的封装抽离
问题:所有的路由配置都堆在main.js中合适么?
目标:将路由模块,单独封装抽离出来。
好处:拆分模块,利于维护,让main.js更清晰简洁
方法:
- 将原main.js中路由相关的配置,提取到src/router/index.js中
- 抽离路由部分后,在原main.js中,只需导入路由规则index.js,注入实例new Vue
- 后续需要配路由规则只需要到src/router/index.js中即可
- 注意在代码抽离时,所引入的文件目录会有变化,通过
@
指代src目录
绝对路径:
- 需要找某个目录下的某个文件时,除了从代码所在的目录出发(通过./在当前级目录,通过../回上一级目录外),可使用绝对路径
- Vue中允许使用标识符
@
来代表绝对路径,输入@/
会有提示,在脚手架环境中应使用@
- 通过
@
指代src目录,可以用于快速引入组件
import Vue from 'vue'
import App from './App.vue'
// 引入路由
import router from './router/index'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
// 在实例中保留路由注入
router
}).$mount('#app')
import Find from '@/views/Find'
import My from '@/views/My'
import Friend from '@/views/Friend'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化
// 创建了一个路由对象
const router = new VueRouter({
// routes 路由规则们
// route 一条路由规则 { path: 路径, component: 组件 }
routes: [
{ path: '/find', component: Find },
{ path: '/my', component: My },
{ path: '/friend', component: Friend },
]
})
// 导出路由对象
export default router
- 总结:
- 路由模块的封装抽离的好处是什么?
- 拆分模块,利于维护
- 以后如何快速引入组件?
- 基于 @ 指代 src 目录,从 src 目录出发找组件
- 路由模块的封装抽离的好处是什么?
声明式导航 & 导航链接
需求:实现导航高亮效果
原js中实现方案:给当前高亮的a标签,添加高亮类名,切换时,将高亮类名移到新的标签,并移除原来的
简单方案:通过router-link标签取代 a 标签,使用vue-router提供的router-link组件实现导航高亮效果
vue-router 提供了一个全局组件 router-link (取代 a 标签)
- ① 能跳转,配置 to 属性指定路径(必须) 。本质还是 a 标签 ,to 无需 #
- ② 能高亮,默认就会提供高亮类名,可以直接设置高亮样式
<template>
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/friend">朋友</router-link>
</div>
<div class="top">
<!-- 路由出口 → 匹配的组件所展示的位置 -->
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {};
</script>
<style>
body {
margin: 0;
padding: 0;
}
.footer_wrap {
position: relative;
left: 0;
top: 0;
display: flex;
width: 100%;
text-align: center;
background-color: #333;
color: #ccc;
}
.footer_wrap a {
flex: 1;
text-decoration: none;
padding: 20px 0;
line-height: 20px;
background-color: #333;
color: #ccc;
border: 1px solid black;
}
// 加类名改颜色
.footer_wrap a.router-link-active {
background-color: purple;
}
.footer_wrap a:hover {
background-color: #555;
}
</style>
- router-link是什么?
- vue-router提供的全局组件, 用于替换 a 标签
- router-link怎么用?
<router-link to="/路径值" ></router-link>
- 必须传入to属性, 指定路由路径值
- router-link好处?
- 能跳转,能高亮 (自带激活时的类名)
声明式导航 - 精确匹配&模糊匹配
声明式导航定义:router-link导航链接
背景:我们发现 router-link 自动给当前导航添加了 两个高亮类名
① router-link-active 模糊匹配 (用的多)
- to="/my" 可以匹配 /my 及 /my/a 及 /my/b 等等/my开头路径下的 ....
- 在嵌套路由/二级页面路由下仍生效
② router-link-exact-active 精确匹配
- to="/my" 仅可以匹配 /my
<template>
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/friend">朋友</router-link>
</div>
<div class="top">
<!-- 路由出口 → 匹配的组件所展示的位置 -->
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {};
</script>
<style>
body {
margin: 0;
padding: 0;
}
.footer_wrap {
position: relative;
left: 0;
top: 0;
display: flex;
width: 100%;
text-align: center;
background-color: #333;
color: #ccc;
}
.footer_wrap a {
flex: 1;
text-decoration: none;
padding: 20px 0;
line-height: 20px;
background-color: #333;
color: #ccc;
border: 1px solid black;
}
/*
router-link-active 模糊匹配(更多)
to="/find" => 地址栏 /find /find/one /find/two ...
router-link-exact-active 精确匹配
to="/find" => 地址栏 /find
*/
.footer_wrap a.router-link-active {
background-color: purple;
}
.footer_wrap a:hover {
background-color: #555;
}
</style>
- 总结:
- router-link 会自动给当前导航添加两个类名,有什么区别呢?
- router-link-active 模糊匹配 (用的多)
- router-link-exact-active 精确匹配
- router-link 会自动给当前导航添加两个类名,有什么区别呢?
声明式导航 - 两个类名
背景:router-link 的 两个高亮类名 太长了(原意是为了防止重名、冲突),我们希望能定制怎么办?
自定义router-link的高亮类名:
- 在路由配置index.js中,配置路由对象的 linkActiveClass属性和linkExactActiveClass属性
自定义router-link高亮类名语法
const router = new VueRouter({
routes: [...],
linkActiveClass: "类名1",
linkExactActiveClass: "类名2"
})
- 总结:
- 如何自定义 router-link 的 两个高亮类名?
- linkActiveClass 模糊匹配 类名自定义
- linkExactActiveClass 精确匹配 类名自定义
- 如何自定义 router-link 的 两个高亮类名?
import Find from '@/views/Find'
import My from '@/views/My'
import Friend from '@/views/Friend'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化
// 创建了一个路由对象
const router = new VueRouter({
// routes 路由规则们
// route 一条路由规则 { path: 路径, component: 组件 }
routes: [
{ path: '/find', component: Find },
{ path: '/my', component: My },
{ path: '/friend', component: Friend },
],
// link自定义高亮类名
linkActiveClass: 'active', // 配置模糊匹配的类名
linkExactActiveClass: 'exact-active' // 配置精确匹配的类名
})
export default router
声明式导航 - 跳转传参
声明式导航定义:router-link导航链接
- 声明式导航是支持跳转传参的
目标:在跳转路由时, 进行传值
声明式导航链接跳转传参的两种语法
- 查询参数传参
- 动态路由传参
- 查询参数传参(地址栏传参)
- ① 语法格式如下
to="/path?参数名=值"
- ② 对应页面组件接收传递过来的值
$route.query.参数名
- 要点:
- 通过
?
携带参数 - 跳转时同时传递参数
- 支持传多个参数,参数间通过
&
进行分隔,与地址栏传参格式一致 - 查询参数,即
.query
,在对应页面组件上通过$route.query.参数名
拿到对应参数的传值,在实例中需要this.$route.query.参数名
- 通常的使用方式
<router-link to="/页面组件?参数名=值">值对应的按钮</router-link>
- 通过
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text">
<button>搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search?key=黑马程序员">黑马程序员</router-link>
<router-link to="/search?key=前端培训">前端培训</router-link>
<router-link to="/search?key=如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'FindMusic'
}
</script>
<style>
.logo-box {
height: 150px;
background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
display: flex;
justify-content: center;
}
.search-box input {
width: 400px;
height: 30px;
line-height: 30px;
border: 2px solid #c4c7ce;
border-radius: 4px 0 0 4px;
outline: none;
}
.search-box input:focus {
border: 2px solid #ad2a26;
}
.search-box button {
width: 100px;
height: 36px;
border: none;
background-color: #ad2a26;
color: #fff;
position: relative;
left: -2px;
border-radius: 0 4px 4px 0;
}
.hot-link {
width: 508px;
height: 60px;
line-height: 60px;
margin: 0 auto;
}
.hot-link a {
margin: 0 5px;
}
</style>
<template>
<div class="search">
<p>搜索关键字: {{ $route.query.key }} </p>
<p>搜索结果: </p>
<ul>
<li>.............</li>
<li>.............</li>
<li>.............</li>
<li>.............</li>
</ul>
</div>
</template>
<script>
export default {
name: 'MyFriend',
// 发请求,对应生命周期钩子函数的created
created () {
// 在created中,获取路由参数
// this.$route.query.参数名 获取
console.log(this.$route.query.key);
}
}
</script>
<style>
.search {
width: 400px;
height: 240px;
padding: 0 20px;
margin: 0 auto;
border: 2px solid #c4c7ce;
border-radius: 5px;
}
</style>
import Home from '@/views/Home'
import Search from '@/views/Search'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/home', component: Home },
{ path: '/search', component: Search }
]
})
export default router
<template>
<div id="app">
<div class="link">
<router-link to="/home">首页</router-link>
<router-link to="/search">搜索页</router-link>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
.link {
height: 50px;
line-height: 50px;
background-color: #495150;
display: flex;
margin: -8px -8px 0 -8px;
margin-bottom: 50px;
}
.link a {
display: block;
text-decoration: none;
background-color: #ad2a26;
width: 100px;
text-align: center;
margin-right: 5px;
color: #fff;
border-radius: 5px;
}
</style>
- 动态路由传参(需要配置路由)
- ① 配置动态路由规则
- 在路由规则
src/router/index.js
中提前配置好:参数名
传递规则 - 在规则路径上使用
:参数名
,来给页面路径直接拼接传值 - 在所属组件
component:
中,写好对应的页面组件
- 在路由规则
const router = new VueRouter({
routes: [
...,
{
path: '/search/:words',
component: Search
}
]
})
- ② 配置导航链接
to="/path/参数值"
- ③ 对应页面组件接收传递过来的值
$route.params.参数名
- 要点:
- 在路由规则
src/router/index.js
中提前配置好:参数名
传递规则 - 直接拼接参数在路径上,跳转时同时传递参数
- 参数,即
.params
,在对应页面组件上通过$route.params.参数名
拿到对应参数的传值,在实例中需要this.$route.params.参数名
- 通常的使用方式
<router-link to="/页面组件/参数值">值对应的按钮</router-link>
- 在路由规则
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text">
<button>搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'FindMusic'
}
</script>
<style>
.logo-box {
height: 150px;
background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
display: flex;
justify-content: center;
}
.search-box input {
width: 400px;
height: 30px;
line-height: 30px;
border: 2px solid #c4c7ce;
border-radius: 4px 0 0 4px;
outline: none;
}
.search-box input:focus {
border: 2px solid #ad2a26;
}
.search-box button {
width: 100px;
height: 36px;
border: none;
background-color: #ad2a26;
color: #fff;
position: relative;
left: -2px;
border-radius: 0 4px 4px 0;
}
.hot-link {
width: 508px;
height: 60px;
line-height: 60px;
margin: 0 auto;
}
.hot-link a {
margin: 0 5px;
}
</style>
<template>
<div class="search">
<p>搜索关键字: {{ $route.params.words }} </p>
<p>搜索结果: </p>
<ul>
<li>.............</li>
<li>.............</li>
<li>.............</li>
<li>.............</li>
</ul>
</div>
</template>
<script>
export default {
name: 'MyFriend',
created () {
// 在created中,获取路由参数
// this.$route.query.参数名 获取查询参数
// this.$route.params.参数名 获取动态路由参数
console.log(this.$route.params.words);
}
}
</script>
<style>
.search {
width: 400px;
height: 240px;
padding: 0 20px;
margin: 0 auto;
border: 2px solid #c4c7ce;
border-radius: 5px;
}
</style>
import Home from '@/views/Home'
import Search from '@/views/Search'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/home', component: Home },
{ path: '/search/:words', component: Search }
]
})
export default router
<template>
<div id="app">
<div class="link">
<router-link to="/home">首页</router-link>
<router-link to="/search">搜索页</router-link>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
.link {
height: 50px;
line-height: 50px;
background-color: #495150;
display: flex;
margin: -8px -8px 0 -8px;
margin-bottom: 50px;
}
.link a {
display: block;
text-decoration: none;
background-color: #ad2a26;
width: 100px;
text-align: center;
margin-right: 5px;
color: #fff;
border-radius: 5px;
}
</style>
比较两种传参方式的区别
- 查询参数传参 (比较适合传多个参数)
- ① 跳转:
to="/path?参数名=值&参数名2=值"
- ② 获取:
$route.query.参数名
- ① 跳转:
- 动态路由传参 (优雅简洁,传单个参数比较方便)
- ① 配置动态路由:
{ path: "/path/:参数名" }
- ② 跳转:
to="/path/参数值"
- ③ 获取:
$route.params.参数名
- ① 配置动态路由:
- 查询参数传参 (比较适合传多个参数)
总结:
声明式导航跳转时, 有几种方式传值给路由页面?
- ① 查询参数传参 (多个参数)
- 跳转:
to="/path?参数名=值"
- 接收:
$route.query.参数名
- 跳转:
- ② 动态路由传参 (简洁优雅)
- 路由:
/path/:参数名
- 跳转:
to="/path/值"
- 接收:
$route.params.参数名
- 路由:
- ① 查询参数传参 (多个参数)
动态路由参数可选符
问题:配了路由
path: "/search/:words"
为什么按下面步骤操作,会未匹配到组件,显示空白?原因:
/search/:words
表示,必须要传参数。- 如果不传参数,也希望匹配,可以加个可选符 "?"
将参数改设为可选性质
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/search/:words?', component: Search }
]
})
import Home from '@/views/Home'
import Search from '@/views/Search'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/home', component: Home },
{ path: '/search/:words?', component: Search }
]
})
export default router
Vue路由 - 重定向
- 问题:网页打开(通常是根路径), url 默认是 / 路径,未匹配到组件时,会出现空白
- 说明:重定向 → 匹配path后, 强制跳转path路径
- 语法:
{ path: 匹配路径, redirect: 重定向到的路径 }
配置路由规则src/router/index.js
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home'},
{ path: '/home', component: Home },
{ path: '/search/:words', component: Search }
]
})
- 附:相当于nginx中的配置
- 效果:通过设置 Nginx 将访问 example.com 时自动重定向到 example.com/zh
在/etc/nginx/nginx.conf中配置
server {
server_name example.com;
rewrite ^/$ /zh permanent;
}
Vue路由 - 404
- 作用:当路径找不到匹配时,给个提示页面
- 位置:配在路由最后
- 语法:
path: "*"
(即任意路径) – 以上规则都不匹配时,就命中最后这个 - 前提:需要预先写好404页并引入
配置路由规则src/router/index.js
import NotFind from '@/views/NotFind'
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/search/:words?', component: Search },
{ path: '*', component: NotFind }
]
})
- 备注:
- 状态码一般后端提供
- axios里面可以写
Vue路由 - 模式设置
问题: 路由的路径看起来不自然, 有#,能否切成真正路径形式?
默认模式:hash路由(默认带#)
- 例如: http://localhost:8080/#/home
history模式:history路由(常用)
- 例如: http://localhost:8080/home (以后上线需要服务器端支持)
history路由模式mode设置src/router/index.js
const router = new VueRouter({
routes: [
...,
],
mode:"history"
})
差异原因:底层实现原理不同
- hash路由:底层基于a标签锚链接实现跳转
- history路由:基于HTML5新增的history API实现
注:
- 使用history路由,在后续项目上线时,需要服务器端配置访问的规则支持,否则出现页面空白的情况
- 两个模式的切换,不影响前端源代码
import Home from '@/views/Home'
import Search from '@/views/Search'
import NotFound from '@/views/NotFound'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化
// 创建了一个路由对象
const router = new VueRouter({
// 注意:一旦采用了 history 模式,地址栏就没有 #,需要后台配置访问规则
mode: 'history',
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ name: 'search', path: '/search/:words?', component: Search },
{ path: '*', component: NotFound }
]
})
export default router
编程式导航 - 基本跳转
问题:点击按钮跳转如何实现?
- 原js实现方式location.herf=
- Vue通过跳路由操作,本质是单页,不能使用location.herf=
编程式导航:用JS代码来进行跳转
编程式导航两种语法:
- ① path 路径跳转
- 通过路径跳转,有配好路径就能实现跳转,不需要额外配置
- $router,指的是大的路由实例对象
- 完整写法时,
this.$router.push({一个path路径对象: '路由路径'})
,适合需要传参时用
- ② name 命名路由跳转
- 路由对象中,除了路径path和组件component以外,还支持name属性
- 需要先在路由规则src/router/index.js中设置,赋予页面路由名字
- ① path 路径跳转
① path 路径跳转 (简易方便)
// 简写
this.$router.push('路由路径')
// 完整写法(传参时用到)
this.$router.push({
path: '路由路径'
})
- ② name 命名路由跳转 (适合 path 路径长的场景)
先在路由设置src/router/index.js中,赋予页面路由名字
{ name: '路由名', path: '/path/xxx', component: XXX },
通过页面路由名字访问相应的路径
this.$router.push({
name: '路由名'
})
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text">
<button @click="goSearch">搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'FindMusic',
methods: {
goSearch () {
// 1. 通过路径的方式跳转
// (1) this.$router.push('路由路径') [简写]
// this.$router.push('/search')
// (2) this.$router.push({ [完整写法]
// path: '路由路径'
// })
// this.$router.push({
// path: '/search'
// })
// 2. 通过命名路由的方式跳转 (需要给路由起名字) 适合长路径
// this.$router.push({
// name: '路由名'
// })
this.$router.push({
name: 'search'
})
}
}
}
</script>
<style>
.logo-box {
height: 150px;
background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
display: flex;
justify-content: center;
}
.search-box input {
width: 400px;
height: 30px;
line-height: 30px;
border: 2px solid #c4c7ce;
border-radius: 4px 0 0 4px;
outline: none;
}
.search-box input:focus {
border: 2px solid #ad2a26;
}
.search-box button {
width: 100px;
height: 36px;
border: none;
background-color: #ad2a26;
color: #fff;
position: relative;
left: -2px;
border-radius: 0 4px 4px 0;
}
.hot-link {
width: 508px;
height: 60px;
line-height: 60px;
margin: 0 auto;
}
.hot-link a {
margin: 0 5px;
}
</style>
import Home from '@/views/Home'
import Search from '@/views/Search'
import NotFound from '@/views/NotFound'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化
// 创建了一个路由对象
const router = new VueRouter({
// 注意:一旦采用了 history 模式,地址栏就没有 #,需要后台配置访问规则
mode: 'history',
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ name: 'search', path: '/search/:words?', component: Search },
{ path: '*', component: NotFound }
]
})
export default router
编程式导航 - 路由传参
问题:点击搜索按钮,跳转需要传参如何实现?
使用场景:将用户在表单中输入的数据,携带传参并跳转,到另一个页面组件中
两种传参方式:查询参数 + 动态路由传参
两种跳转方式,对于两种传参方式都支持:
- ① path 路径跳转传参
- ② name 命名路由跳转传参
① path 路径跳转传参
查询参数query传参(地址栏拼?
)
// 可以直接拼
this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')
// 支持对象写法
this.$router.push({
path: '/路径',
query: {
参数名1: '参数值1',
参数名2: '参数值2'
}
})
// 接收
`$route.query.参数名`
动态路由传参 (需要配动态路由)
this.$router.push('/路径/参数值')
this.$router.push({
path: '/路径/参数值'
})
// 接收,需要配路由规则
`$route.params.参数名`
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input v-model="inpValue" type="text">
<button @click="goSearch">搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'FindMusic',
data () {
return {
inpValue: ''
}
},
methods: {
goSearch () {
// 1. 通过路径的方式跳转
// (1) this.$router.push('路由路径') [简写]
this.$router.push('路由路径?参数名=参数值')
// this.$router.push('/search')
// 查询参数query传参(地址栏拼`?`)
this.$router.push(`/search?key=${this.inpValue}`)
// 动态路由传参
this.$router.push(`/search/${this.inpValue}`)
// (2) 多参数场景
// this.$router.push({ [完整写法] 更适合传参
// path: '路由路径'
// query: {
// 参数名: 参数值,
// 参数名: 参数值
// }
// })
// 查询参数query传参
this.$router.push({
path: '/search',
query: {
key: this.inpValue
}
})
// 动态路由传参
this.$router.push({
path: `/search/${this.inpValue}`
})
}
}
}
</script>
<style>
.logo-box {
height: 150px;
background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
display: flex;
justify-content: center;
}
.search-box input {
width: 400px;
height: 30px;
line-height: 30px;
border: 2px solid #c4c7ce;
border-radius: 4px 0 0 4px;
outline: none;
}
.search-box input:focus {
border: 2px solid #ad2a26;
}
.search-box button {
width: 100px;
height: 36px;
border: none;
background-color: #ad2a26;
color: #fff;
position: relative;
left: -2px;
border-radius: 0 4px 4px 0;
}
.hot-link {
width: 508px;
height: 60px;
line-height: 60px;
margin: 0 auto;
}
.hot-link a {
margin: 0 5px;
}
</style>
<template>
<div class="search">
// 查询参数接收
<p>搜索关键字: {{ $route.query.key }} </p>
// 动态路由接收
<p>搜索关键字: {{ $route.params.words }} </p>
<p>搜索结果: </p>
<ul>
<li>.............</li>
<li>.............</li>
<li>.............</li>
<li>.............</li>
</ul>
</div>
</template>
<script>
export default {
name: 'MyFriend',
created () {
// 在created中,获取路由参数
// this.$route.query.参数名 获取查询参数
// this.$route.params.参数名 获取动态路由参数
// console.log(this.$route.params.words);
}
}
</script>
<style>
.search {
width: 400px;
height: 240px;
padding: 0 20px;
margin: 0 auto;
border: 2px solid #c4c7ce;
border-radius: 5px;
}
</style>
- ② name 命名路由跳转传参
查询参数query传参(地址栏拼?
)
this.$router.push({
name: '路由名字',
query: {
参数名1: '参数值1',
参数名2: '参数值2'
}
})
// 接收
`$route.query.参数名`
动态路由传参 (需要配动态路由)
this.$router.push({
name: '路由名字',
params: {
参数名: '参数值',
}
})
// 接收,需要配路由规则
`$route.params.参数名`
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input v-model="inpValue" type="text">
<button @click="goSearch">搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'FindMusic',
data () {
return {
inpValue: ''
}
},
methods: {
goSearch () {
// 2. 通过命名路由的方式跳转 (需要给路由起名字) 适合长路径
// (1) 查询参数传参
//this.$router.push({
// name: '路由名'
// query: { 参数名: 参数值 },
// params: { 参数名: 参数值 }
//})
this.$router.push({
name: 'search',
query: {
key: this.inpValue
}
})
// (2) 动态路由传参
this.$router.push({
name: 'search',
params: {
// 与路由对应
words: this.inpValue
}
})
}
}
}
</script>
<style>
.logo-box {
height: 150px;
background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
display: flex;
justify-content: center;
}
.search-box input {
width: 400px;
height: 30px;
line-height: 30px;
border: 2px solid #c4c7ce;
border-radius: 4px 0 0 4px;
outline: none;
}
.search-box input:focus {
border: 2px solid #ad2a26;
}
.search-box button {
width: 100px;
height: 36px;
border: none;
background-color: #ad2a26;
color: #fff;
position: relative;
left: -2px;
border-radius: 0 4px 4px 0;
}
.hot-link {
width: 508px;
height: 60px;
line-height: 60px;
margin: 0 auto;
}
.hot-link a {
margin: 0 5px;
}
</style>
<template>
<div class="search">
// 查询参数接收
<p>搜索关键字: {{ $route.query.key }} </p>
// 动态路由接收
<p>搜索关键字: {{ $route.params.words }} </p>
<p>搜索结果: </p>
<ul>
<li>.............</li>
<li>.............</li>
<li>.............</li>
<li>.............</li>
</ul>
</div>
</template>
<script>
export default {
name: 'MyFriend',
created () {
// 在created中,获取路由参数
// this.$route.query.参数名 获取查询参数
// this.$route.params.参数名 获取动态路由参数
// console.log(this.$route.params.words);
}
}
</script>
<style>
.search {
width: 400px;
height: 240px;
padding: 0 20px;
margin: 0 auto;
border: 2px solid #c4c7ce;
border-radius: 5px;
}
</style>
面经基础版
- 分析:配路由 + 实现功能
- 配路由
- ① 首页 和 面经详情,两个一级路由
- 两个大页面,首页下方4个按钮是在首页内部切组件
- 两个大页面,属于最顶层路由,一级路由
- 首页一级路由:路径/,组件Layout
- 面经详情一级路由:路径/detail',组件ArticleDetail
- 首页下的二级路由有4个:article、collect、like、user
- ② 首页内嵌四个可切换页面 (嵌套二级路由)
- 嵌套二级路由语法1:在
children: [数组包对象{}]
配规则, - 嵌套二级路由语法2:准备二级路由出口
- 嵌套二级路由语法1:在
import Article from '@/views/Article.vue'
import ArticleDetail from '@/views/ArticleDetail.vue'
import Collect from '@/views/Collect.vue'
import Layout from '@/views/Layout.vue'
import Like from '@/views/Like.vue'
import User from '@/views/User.vue'
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
component: Layout,
children: [
{
path: '/article',
component: Article,
},
{
path: '/collect',
component: Collect,
},
{
path: '/like',
component: Like,
},
{
path: '/user',
component: User,
},
],
},
{
path: '/detail',
component: ArticleDetail,
},
],
})
export default router
<template>
<div class="h5-wrapper">
<div class="content">
<!-- 二级路由出口 -->
<router-view></router-view>
</div>
<nav class="tabbar">
<!-- 导航高亮 -->
<router-link to="/article">面经</router-link>
<router-link to="/collect">收藏</router-link>
<router-link to="/like">喜欢</router-link>
<router-link to="/user">我的</router-link>
</nav>
</div>
</template>
<script>
export default {
name: "LayoutPage",
}
</script>
<style>
body {
margin: 0;
padding: 0;
}
</style>
<style lang="less" scoped>
.h5-wrapper {
.content {
margin-bottom: 51px;
}
.tabbar {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
display: flex;
background: #fff;
border-top: 1px solid #e4e4e4;
a {
flex: 1;
text-decoration: none;
font-size: 14px;
color: #333;
-webkit-tap-highlight-color: transparent;
}
a.router-link-active{
color: orange;
}
}
}
</style>
- 实现功能
- ① 首页请求动态渲染
- 安装 axios 通信
- 对接口文档,确认请求地址、请求方式、请求参数
- Vue中通过created钩子,发请求,获取参数,存起来json
- 页面动态渲染
- ② 跳转传参 到 详情页,详情页渲染(传文章id,两种传参方式)
- 查询参数传参(更适合多个参数):
?参数=参数值
,接收this.$route.query.参数名
- 动态路由传参(单个参数更方便):先
改造路由
,再/路径/参数
,接收this.$route.params.参数名
- 查询参数传参(更适合多个参数):
- ③ 组件缓存,优化性能
- 优化bug,首页重定向到文章组件
- 返回上一页
@click="$router.back()"
- 优化渲染流程,在点击进入文章详情页时,有一瞬间是空白,原因是:发请求需要时间,当请求结果还没返回时(也就是数据还没写入
data() {return {article:{}}}
中),实际上article:{}
是个空对象,拿不到数据,因此页面出现短暂的空白 - 正确合理的逻辑:在数据回来前,二级路由页面不应该做渲染,应该等数据完全回来后再渲染,甚至后期可以在二级路由页加一个loading效果
- 可以通过在整个大的div盒子上,加上v-if判断,当拿到id时(也就是有数据返回时),再去渲染下面内容
pnpm add axios
pnpm dev
① 首页请求动态渲染
<template>
<div class="article-page">
<div v-for="item in articles" :key="item.id" class="article-item">
<div class="head">
<img :src="item.creatorAvatar" alt="" />
<div class="con">
<p class="title">{{item.stem}}</p>
<p class="other">{{ item.creatorName }} | {{ item.createdAt }}</p>
</div>
</div>
<div class="body">
{{ item.content }}
</div>
<div class="foot">点赞 {{ item.likeCount }} | 浏览 {{ item.views }}</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
// 请求地址: https://mock.boxuegu.com/mock/3083/articles
// 请求方式: get
export default {
name: 'ArticlePage',
data () {
return {
// 存数据
articles:[]
}
},
// 发请求
async created() {
const res = await axios.get('https://mock.boxuegu.com/mock/3083/articles')
console.log(res);
this.articles = res.data.result.rows
console.log(this.articles);
}
}
</script>
<style lang="less" scoped>
.article-page {
background: #f5f5f5;
}
.article-item {
margin-bottom: 10px;
background: #fff;
padding: 10px 15px;
.head {
display: flex;
img {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
}
.con {
flex: 1;
overflow: hidden;
padding-left: 15px;
p {
margin: 0;
line-height: 1.5;
&.title {
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
white-space: nowrap;
}
&.other {
font-size: 10px;
color: #999;
}
}
}
}
.body {
font-size: 14px;
color: #666;
line-height: 1.6;
margin-top: 10px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.foot {
font-size: 12px;
color: #999;
margin-top: 10px;
}
}
</style>
②.1 跳转传参到详情页
<template>
<div class="article-page">
<div v-for="item in articles" :key="item.id" @click="$router.push(`/detail?id=${item.id}`)" class="article-item">
<div class="head">
<img :src="item.creatorAvatar" alt="" />
<div class="con">
<p class="title">{{item.stem}}</p>
<p class="other">{{ item.creatorName }} | {{ item.createdAt }}</p>
</div>
</div>
<div class="body">
{{ item.content }}
</div>
<div class="foot">点赞 {{ item.likeCount }} | 浏览 {{ item.views }}</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
// 请求地址: https://mock.boxuegu.com/mock/3083/articles
// 请求方式: get
export default {
name: 'ArticlePage',
data () {
return {
// 存数据
articles:[]
}
},
// 发请求
async created() {
const res = await axios.get('https://mock.boxuegu.com/mock/3083/articles')
console.log(res);
this.articles = res.data.result.rows
console.log(this.articles);
}
}
</script>
<style lang="less" scoped>
.article-page {
background: #f5f5f5;
}
.article-item {
margin-bottom: 10px;
background: #fff;
padding: 10px 15px;
.head {
display: flex;
img {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
}
.con {
flex: 1;
overflow: hidden;
padding-left: 15px;
p {
margin: 0;
line-height: 1.5;
&.title {
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
white-space: nowrap;
}
&.other {
font-size: 10px;
color: #999;
}
}
}
}
.body {
font-size: 14px;
color: #666;
line-height: 1.6;
margin-top: 10px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.foot {
font-size: 12px;
color: #999;
margin-top: 10px;
}
}
</style>
<template>
<div class="article-detail-page">
<nav class="nav"><span class="back"><</span> 面经详情</nav>
<header class="header">
<h1>百度前端面经</h1>
<p>2022-01-20 | 315 浏览量 | 44 点赞数</p>
<p>
<img
src="http://teachoss.itheima.net/heimaQuestionMiniapp/%E5%AE%98%E6%96%B9%E9%BB%98%E8%AE%A4%E5%A4%B4%E5%83%8F%402x.png"
alt=""
/>
<span>青春少年</span>
</p>
</header>
<main class="body">
虽然百度这几年发展势头落后于AT, 甚至快被京东赶上了,毕竟瘦死的骆驼比马大,
面试还是相当有难度和水准的, 一面.....
</main>
</div>
</template>
<script>
// 请求地址: https://mock.boxuegu.com/mock/3083/articles/:id
// 请求方式: get
export default {
name: "ArticleDetailPage",
data() {
return {
}
},
created() {
console.log(this.$route.query.id);
}
}
</script>
<style lang="less" scoped>
.article-detail-page {
.nav {
height: 44px;
border-bottom: 1px solid #e4e4e4;
line-height: 44px;
text-align: center;
.back {
font-size: 18px;
color: #666;
position: absolute;
left: 10px;
top: 0;
transform: scale(1, 1.5);
}
}
.header {
padding: 0 15px;
p {
color: #999;
font-size: 12px;
display: flex;
align-items: center;
}
img {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
}
}
.body {
padding: 0 15px;
}
}
</style>
②.2 跳转传参到详情页
import Article from '@/views/Article.vue'
import ArticleDetail from '@/views/ArticleDetail.vue'
import Collect from '@/views/Collect.vue'
import Layout from '@/views/Layout.vue'
import Like from '@/views/Like.vue'
import User from '@/views/User.vue'
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
component: Layout,
children: [
{
path: '/article',
component: Article,
},
{
path: '/collect',
component: Collect,
},
{
path: '/like',
component: Like,
},
{
path: '/user',
component: User,
},
],
},
// {
// path: '/detail',
// component: ArticleDetail,
// },
{
path: '/detail/:id',
component: ArticleDetail,
},
// {
// path: '/',
// name: 'home',
// component: HomeView,
// },
// {
// path: '/about',
// name: 'about',
// // route level code-splitting
// // this generates a separate chunk (About.[hash].js) for this route
// // which is lazy-loaded when the route is visited.
// component: () => import('../views/AboutView.vue'),
// },
],
})
export default router
<template>
<div class="article-page">
<div v-for="item in articles" :key="item.id" @click="$router.push(`/detail/${item.id}`)" class="article-item">
<div class="head">
<img :src="item.creatorAvatar" alt="" />
<div class="con">
<p class="title">{{item.stem}}</p>
<p class="other">{{ item.creatorName }} | {{ item.createdAt }}</p>
</div>
</div>
<div class="body">
{{ item.content }}
</div>
<div class="foot">点赞 {{ item.likeCount }} | 浏览 {{ item.views }}</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
// 请求地址: https://mock.boxuegu.com/mock/3083/articles
// 请求方式: get
export default {
name: 'ArticlePage',
data () {
return {
// 存数据
articles:[]
}
},
// 发请求
async created() {
const res = await axios.get('https://mock.boxuegu.com/mock/3083/articles')
console.log(res);
this.articles = res.data.result.rows
console.log(this.articles);
}
}
</script>
<style lang="less" scoped>
.article-page {
background: #f5f5f5;
}
.article-item {
margin-bottom: 10px;
background: #fff;
padding: 10px 15px;
.head {
display: flex;
img {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
}
.con {
flex: 1;
overflow: hidden;
padding-left: 15px;
p {
margin: 0;
line-height: 1.5;
&.title {
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
white-space: nowrap;
}
&.other {
font-size: 10px;
color: #999;
}
}
}
}
.body {
font-size: 14px;
color: #666;
line-height: 1.6;
margin-top: 10px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.foot {
font-size: 12px;
color: #999;
margin-top: 10px;
}
}
</style>
<template>
<div class="article-detail-page">
<nav class="nav"><span class="back"><</span> 面经详情</nav>
<header class="header">
<h1>百度前端面经</h1>
<p>2022-01-20 | 315 浏览量 | 44 点赞数</p>
<p>
<img
src="http://teachoss.itheima.net/heimaQuestionMiniapp/%E5%AE%98%E6%96%B9%E9%BB%98%E8%AE%A4%E5%A4%B4%E5%83%8F%402x.png"
alt=""
/>
<span>青春少年</span>
</p>
</header>
<main class="body">
虽然百度这几年发展势头落后于AT, 甚至快被京东赶上了,毕竟瘦死的骆驼比马大,
面试还是相当有难度和水准的, 一面.....
</main>
</div>
</template>
<script>
// 请求地址: https://mock.boxuegu.com/mock/3083/articles/:id
// 请求方式: get
export default {
name: "ArticleDetailPage",
data() {
return {
}
},
created() {
console.log(this.$route.params.id);
}
}
</script>
<style lang="less" scoped>
.article-detail-page {
.nav {
height: 44px;
border-bottom: 1px solid #e4e4e4;
line-height: 44px;
text-align: center;
.back {
font-size: 18px;
color: #666;
position: absolute;
left: 10px;
top: 0;
transform: scale(1, 1.5);
}
}
.header {
padding: 0 15px;
p {
color: #999;
font-size: 12px;
display: flex;
align-items: center;
}
img {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
}
}
.body {
padding: 0 15px;
}
}
</style>
③.1 优化bug,首页重定向到文章组件
routes: [
{
path: '/',
component: Layout,
redirect: '/article',
children: [。。。。。]
}
]
③.2 返回上一页@click="$router.back()"
<nav class="nav"><span @click="$router.back()" class="back"><</span> 面经详情</nav>
<template>
<div class="article-detail-page" v-if="article.id">
<nav class="nav"><span @click="$router.back()" class="back"><</span> 面经详情</nav>
<header class="header">
<h1>{{ article.stem }}</h1>
<p>{{ article.createdAt }} | {{ article.views }} 浏览量 | {{ article.likeCount }} 点赞数</p>
<p>
<img
:src="article.creatorAvatar"
alt="" />
<span>{{ article.creatorName }}</span>
</p>
</header>
<main class="body">
{{ article.content }}
</main>
</div>
</template>
<script>
import axios from 'axios';
// 请求地址: https://mock.boxuegu.com/mock/3083/articles/:id
// 请求方式: get
export default {
name: "ArticleDetailPage",
data() {
return {
article:{}
}
},
async created() {
const id = this.$route.query.id
// const res = await axios.get(`https://mock.boxuegu.com/mock/3083/articles/${id}`)
// console.log(res);
const {data:{result}}= await axios.get(`https://mock.boxuegu.com/mock/3083/articles/${id}`)
this.article = result
console.log(this.article);
}
}
</script>
<style lang="less" scoped>
.article-detail-page {
.nav {
height: 44px;
border-bottom: 1px solid #e4e4e4;
line-height: 44px;
text-align: center;
.back {
font-size: 18px;
color: #666;
position: absolute;
left: 10px;
top: 0;
transform: scale(1, 1.5);
}
}
.header {
padding: 0 15px;
p {
color: #999;
font-size: 12px;
display: flex;
align-items: center;
}
img {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
}
}
.body {
padding: 0 15px;
}
}
</style>
组件缓存 keep-alive
问题:从面经 点到 详情页,又点返回,数据重新加载了 → 希望回到原来的位置
需求:返回时,希望回到原来的位置,即点进去文章详情组件前在主页的下拉浏览位置
原因:路由跳转后,组件被销毁了,返回回来组件又被重建了,所以数据重新被加载了
备注:
- 默认情况下的路由跳转,会销毁跳转前的组件
- 跳转后再通过后退返回跳转前的页面时,由于之前的组件被销毁了,所以本质上是又发了请求重新加载数据
解决:利用 keep-alive ,将原有已经加载过的数据和组件,缓存下来
认识
- keep-alive是什么
- keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
- keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。
- keep-alive的优点
- 在组件切换过程中 把切换出去的组件保留在内存中,防止重复渲染DOM,
- 减少加载时间及性能消耗,提高用户体验性。
- 是内置的组件,使用时,在结构中,通过标签对包裹即可
<template>
<div class="h5-wrapper">
<keep-alive>
<router-view></router-view>
</keep-alive>
</div>
</template>
- 问题:缓存了所有被切换的组件
- 通过上述方法使用时,一级路由页全部被缓存,但实际上只需要缓存Layout页,不需要缓存另一个一级路由detail页
- keep-alive的三个属性
- ① include: 组件名数组,只有匹配的组件会被缓存
- ② exclude: 组件名数组,任何匹配的组件都不会被缓存
- ③ max : 最多可以缓存多少组件实例
- 备注:
- 使用时,需要给组件设置组件名name属性,name的优先级更高
- 如果组件没有name名字属性,才会去匹配文件名
- 本质上,被keep-alive包裹的组件,多了两个生命周期钩子:activated和deactived
<template>
<div class="h5-wrapper">
<keep-alive :include="['LayoutPage']">
<router-view></router-view>
</keep-alive>
</div>
</template>
<script setup>
</script>
<template>
<div class="h5-wrapper" >
<!-- 所有一级路由匹配的组件都会被缓存 -->
<!-- <keep-alive :include="['LayoutPage']" > -->
<!-- 当缓存的数据比较多,可以通过数组来写 -->
<keep-alive :include="keepArr">
<!-- 一级路由出口 -->
<router-view></router-view>
</keep-alive>
</div>
</template>
<script>
export default {
name:'h5-warpper',
data() {
return {
// 缓存的组件名数组
keepArr:['LayoutPage']
}
}
}
</script>
<style scoped>
header {
line-height: 1.5;
max-height: 100vh;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
nav {
width: 100%;
font-size: 12px;
text-align: center;
margin-top: 2rem;
}
nav a.router-link-exact-active {
color: var(--color-text);
}
nav a.router-link-exact-active:hover {
background-color: transparent;
}
nav a {
display: inline-block;
padding: 0 1rem;
border-left: 1px solid var(--color-border);
}
nav a:first-of-type {
border: 0;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
nav {
text-align: left;
margin-left: -1rem;
font-size: 1rem;
padding: 1rem 0;
margin-top: 1rem;
}
}
</style>
- keep-alive的使用会触发两个生命周期函数
actived(激活时/即组件被看到时,触发)当组件被激活(使用)的时候触发 → 进入这个页面的时候触发
deactived(失活时/离开页面组件看不见时触发)当组件不被使用的时候触发 → 离开这个页面的时候触发
组件缓存后就不会执行组件的created, mounted, destroyed 等钩子了,所以其提供了actived 和deactived钩子,帮我们实现业务需求
activated () {
console.log('actived 激活 → 进入页面');
},
deactivated() {
console.log('deactived 失活 → 离开页面');
}
<template>
<div class="h5-wrapper">
<div class="content">
<!-- 二级路由出口 -->
<router-view></router-view>
</div>
<nav class="tabbar">
<!-- 导航高亮 -->
<router-link to="/article">面经</router-link>
<router-link to="/collect">收藏</router-link>
<router-link to="/like">喜欢</router-link>
<router-link to="/user">我的</router-link>
</nav>
</div>
</template>
<script>
export default {
name: "LayoutPage",
// 组件一旦缓存了,就不会执行组件的created、mounted、destroyed等钩子
// 因此提供了actived和deactived
created() {
console.log('created 组件被加载了');
},
mounted() {
console.log('mounted dom被渲染完了');
},
destroyed() {
console.log('destroyed 组件被销毁了');
},
activated () {
alert('你好,欢迎回到首页')
console.log('actived 组件被激活了/看到页面了 → 进入页面');
},
deactivated() {
console.log('deactived 组件失活 → 离开页面');
}
}
</script>
<style>
body {
margin: 0;
padding: 0;
}
</style>
<style lang="less" scoped>
.h5-wrapper {
.content {
margin-bottom: 51px;
}
.tabbar {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
display: flex;
background: #fff;
border-top: 1px solid #e4e4e4;
a {
flex: 1;
text-decoration: none;
font-size: 14px;
color: #333;
-webkit-tap-highlight-color: transparent;
}
a.router-link-active{
color: orange;
}
}
}
</style>
注意:参考vue3最新实例
总结:
- keep-alive是什么
- Vue 的内置组件,包裹动态组件时,可以缓存
- keep-alive的优点
- 组件切换过程中 把切换出去的组件保留在内存中(提升性能)
- keep-alive的三个属性 (了解)
- ① include : 组件名数组,只有匹配的组件会被缓存
- ② exclude : 组件名数组,任何匹配的组件都不会被缓存
- ③ max : 最多可以缓存多少组件实例
- keep-alive的使用会触发两个生命周期函数 (了解)
- activated 当组件被激活(使用)的时候触发 → 进入页面触发
- deactivated 当组件不被使用的时候触发 → 离开页面触发
- 意义:部署一些进入页面/离开页面时触发的功能
面经基础版项目案例总结
- 项目案例实现的基本步骤分哪两大步?
- ① 配路由
- ② 实现页面功能
- 嵌套路由的关键配置项是什么?
- children
- 路由传参两种方式?
- ① 查询参数传参,$route.query.参数名 (适合多个参数)
- ② 动态路由传参,$route.params.参数名 (更简洁直观)
- 缓存组件可以用哪个内置组件?
- keep-alive
- 三个属性: include exclude max
- 两个钩子: activated deactivated
自定义创建项目
目标:基于 VueCli 自定义创建项目架子
步骤:
备注:
- Babel:语法翻译、降级
- router:默认架起预设的route配置,后续直接配规则即可
- css:css预处理器,包括less、sass
- Linter:ESlint 代码规范
使用:
- 路径下:
vue create 项目名称
- vue cli中,选择自定义配置:Manually select features
- 操作:上下箭头选光标,空格选定或取消,回车确认下一步
- 无分号规范(标准化)选项:ESLint + Standard config
- 保存时校验:Lint on save
- 将配置文件放在单独的文件中:In dedicated config files
- 路径下:
pnpm是一款备受关注的新的包管理器,当下载了pnpm之后,通过vue-cli脚手架生成vue项目时,你可能会看到包管理器选择
假定我们选择了pnpm之后,下次安装新项目时直接会通过pnpm下载依赖,而不是再次询问!
那怎么才能更改呢?
=================================================
方法一、 仅对本次创建的项目指定某包管理器
vue create 命令有一些可选项,你可以通过运行以下命令进行探索:
vue create --help
用法:create [options] <app-name>
创建一个由 `vue-cli-service` 提供支持的新项目
选项:
-p, --preset <presetName> 忽略提示符并使用已保存的或远程的预设选项
-d, --default 忽略提示符并使用默认预设选项
-i, --inlinePreset <json> 忽略提示符并使用内联的 JSON 字符串预设选项
-m, --packageManager <command> 在安装依赖时使用指定的 npm 客户端
-r, --registry <url> 在安装依赖时使用指定的 npm registry
-g, --git [message] 强制 / 跳过 git 初始化,并可选的指定初始化提交信息
-n, --no-git 跳过 git 初始化
-f, --force 覆写目标目录可能存在的配置
-c, --clone 使用 git clone 获取远程预设选项
-x, --proxy 使用指定的代理创建项目
-b, --bare 创建项目时省略默认组件中的新手指导信息
-h, --help 输出使用帮助信息
因此,假如你这次项目想通过npm来安装你可以
vue create -m npm test
======================================
方法二、找到.vuerc文件并修改
~/.vuerc
被保存的 preset 将会存在用户的 home 目录下一个名为 .vuerc 的 JSON 文件里。如果你想要修改被保存的 preset / 选项,可以编辑这个文件。
在项目创建的过程中,你也会被提示选择喜欢的包管理器或使用淘宝 npm 镜像源以更快地安装依赖。这些选择也将会存入 ~/.vuerc。
这下我们知道了为什么选择了包管理器之后,下次会默认通过之前选择的方式下载依赖了!
我们找到.vuerc文件, 我的是在C:\Users\xxx 下,
{
"useTaobaoRegistry": true,
"latestVersion": "4.5.15",
"lastChecked": 1639917344017,
"packageManager": "pnpm" //改这里!!!
}
直接修改packageManager。或者删了,下次安装时会继续让你选择安装方式,保存在此文件中。
======================================
方法三、通过vue-cli的命令修改
查看vue-cli支持的命令
vue --help
1
Commands:
create [options] <app-name> create a new project powered by vue-cli-service
add [options] <plugin> [pluginOptions] install a plugin and invoke its generator in an already created project
invoke [options] <plugin> [pluginOptions] invoke the generator of a plugin in an already created project
inspect [options] [paths...] inspect the webpack config in a project with vue-cli-service
serve [options] [entry] serve a .js or .vue file in development mode with zero config
build [options] [entry] build a .js or .vue file in production mode with zero config
ui [options] start and open the vue-cli ui
init [options] <template> <app-name> generate a project from a remote template (legacy API, requires @vue/cli-init)
config [options] [value] inspect and modify the config
outdated [options] (experimental) check for outdated vue cli service / plugins
upgrade [options] [plugin-name] (experimental) upgrade vue cli service / plugins
migrate [options] [plugin-name] (experimental) run migrator for an already-installed cli plugin
info print debugging information about your environment
Run vue <command> --help for detailed usage of given command.
输入vue config --help查看config 下的命令
Usage: config [options] [value]
inspect and modify the config
Options:
-g, --get <path> get value from option
-s, --set <path> <value> set option value
-d, --delete <path> delete option from config
-e, --edit open config with default editor
--json outputs JSON result only
-h, --help output usage information
输入vue config 你会发现输出的就是.vuerc文件的值!
Resolved path: C:\Users\xxx\.vuerc
{
"useTaobaoRegistry": true,
"latestVersion": "4.5.15",
"lastChecked": 1639917344017,
"packageManager": "pnpm"
}
假如我们要把包管理器改为npm,我们可以输入
vue config --set packageManager npm
改完之后, 通过
vue config --get packageManager
验证一下!嗯!的确改为npm了!
总结
1.我们学会了使用–help 输出帮助内容
2.我们找到了vue-cli的预置配置保存在哪
3.我们学会了通过三种方法更改包管理器
ESlint 代码规范
目标:认识代码规范
代码规范含义:一套写代码的约定规则。
- 例如:"赋值符号的左右是否需要空格" "一句结束是否是要加;" ...
意义:老话说:"没有规矩不成方圆" → 正规的团队 需要 统一的编码风格
下面是这份规则中的一小部分:
- 字符串使用单引号
'abc'
- 无分号
const name = 'zs'
- 关键字后加空格
if (name = 'ls') { ... }
- 函数名后加空格
function name (arg) { ... }
- 坚持使用全等
===
摒弃 == (会出现隐式类型转换)
- 字符串使用单引号
代码规范错误认识&解决
目标:学会解决代码规范错误
报错信息:包含行数和第几个字符
两种解决方案:
- ① 手动修正
- 根据错误提示来一项一项手动修改纠正。
- 如果你不认识命令行中的语法报错是什么意思,根据错误代码去 ESLint 规则表 中查找其具体含义。
- ② 自动修正
- 基于 vscode 插件 ESLint 高亮错误,并通过配置 自动 帮助我们修复错误。报错会有下红波浪线
- 基于 webstorm 的操作:文件--设置--工具--保存时的操作--勾选
运行ESLint --fix
- ① 手动修正
配置自动修正:
- 点左下角设置(快捷键
ctrl + ,
), - 在设置页中,点击右上角
打开设置json
(或直接>setting
)
- 点左下角设置(快捷键
- 手动修正案例: