Vue2-面经基础版-案例【黑马程序员】
一、案例效果分析
案例效果:
底下四个导航切换对应的四个页面
第一个渲染出来的列表中点击跳转详情
分析:配路由+实现功能
- 配路由:
- 【首页】和【面经详情】,这两个一级路由(页面要么渲染首页,要么渲染详情页,并且二者可以通过一定方式来回跳转)
- 【首页】内嵌四个可切换页面(嵌套*二级路由*)
- 实现功能:
- 【首页】请求渲染
- 跳转传参到详情页(不同的文章列表跳转的详情页不同,携带面经文章id,只有一个参数,直接在地址栏携带动态传参比较方便),【详情页】渲染
- 组件缓存,优化性能
二、素材
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router
}).$mount('#app')
App.vue
<template>
<div class="h5-wrapper">
<!--路由出口-->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "h5-wrapper",
}
</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;
&.router-link-active {
color: #fa0;
}
}
}
}
</style>
router/index.js
import Vue from 'vue'
import VueRouter from "vue-router";
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
]
})
export default router
views/
=====Article.vue=====
<template>
<div class="article-page">
<div
class="article-item"
>
<div class="head">
<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="" />
<div class="con">
<p class="title">百度前端面经</p>
<p class="other">青春, 那么骚 | 2022-01-20</p>
</div>
</div>
<div class="body">
虽然百度这几年发展势头落后于AT,甚至快被京东赶上了,毕竟瘦死的骆驼比马大,面试还是相当有难度和水准的。一面1.询问你的项目经验、学习经历、主修语言(照实答)2.解释ES6的暂时性死区( let 和 var 的区别)3.箭头函数、闭包、异步(老生常谈,参见上文)4.高阶函数(呃……我真不太清楚这是啥,听起来挺像闭包的)5.求N的阶乘末尾有多少个0,在线码代码或讲思路(求因数,统计2、5、10的个数
</div>
<div class="foot">点赞 44 | 浏览 315</div>
</div>
</div>
</template>
<script>
// 请求地址: https://mock.boxuegu.com/mock/3083/articles
// 请求方式: get
export default {
name: 'ArticlePage',
data () {
return {
}
}
}
</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>
=====ArticleDetail.vue=====
<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 {
}
}
}
</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>
=====Layout.vue=====
<template>
<div class="h5-wrapper">
<div class="content">
内容
</div>
<nav class="tabbar">
<a href="#/article">面经</a>
<a href="#/collect">收藏</a>
<a href="#/like">喜欢</a>
<a href="#/user">我的</a>
</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;
}
}
}
</style>
=====Collect.vue=====
<template>
<div>Collect</div>
</template>
<script>
export default {
name: 'CollectPage'
}
</script>
=====Like.vue=====
<template>
<div>Like</div>
</template>
<script>
export default {
name: 'LikePage'
}
</script>
=====User.vue=====
<template>
<div>User</div>
</template>
<script>
export default {
name: 'UserPage'
}
</script>
三、路由配置
聚焦于:一级路由对应内容&子路由页面
①. 一级路由配置
代码实现
在router/index.js
文件中进行
…… ……
//导入一级路由
import Layout from '@/views/Layout.vue'
import ArticleDetail from '@/views/ArticleDetail.vue'
…… ……
//
const router = new VueRouter({
routes: [
{
path: '/', //访问主页
component: Layout //渲染组件
},
{
path: '/ArticleDetail', //访问详情页
component: ArticleDetail
}
]
})
②. 二级路由配置
二级路由也叫嵌套路由,还可以嵌套三级、四级…
-
使用场景
当在页面中点击链接跳转,只是部分内容切换时
-
语法
- 在一级路由下,配置==children属性==即可
- 配置二级路由的出口
const router = new VueRouter({
routes: [
{
path: '/',
component: Layout,
children:[
//children中的配置项 跟一级路由中的配置项一模一样
{path:'xxxx',component:xxxx.vue},
{path:'xxxx',component:xxxx.vue},
]
}
]
})
注意:
- 一级的路由path 需要加
/
; 二级路由的path可加可不加/
- 二级路由对应的组件渲染到哪个一级路由下,children就配置到哪个路由下边
代码实现-二级路由
在router/index.js
文件中进行
…… ……
import Article from '@/views/Article.vue'
import Collect from '@/views/Collect.vue'
import Like from '@/views/Like.vue'
import User from '@/views/User.vue'
…… ……
const router = new VueRouter({
routes: [
{
path:'/',
component: Layout,
children:[
{
path:'/Article',
component:Article
},
{
path:'/Collect',
component:Collect
},
{
path:'/Like',
component:Like
},
{
path:'/User',
component:User
}
]
},
{
path:'/ArticleDetail',
component:ArticleDetail
}
]
})
在Layout.vue
文件中进行
注意:配置了嵌套路由,一定配置对应的路由出口,否则不会渲染出对应的组件
代码实现-路由出口
<template>
<div class="h5-wrapper">
<div class="content">
内容<!--在此处进行动态切换-->
<!--记得准备【路由出口】-->
<router-view></router-view>
<!--记得准备【路由出口】-->
</div>
<nav class="tabbar">
<a href="#/article">面经</a>
<a href="#/collect">收藏</a>
<a href="#/like">喜欢</a>
<a href="#/user">我的</a>
</nav>
</div>
</template>
四、二级导航高亮
1. 实现思路
- 将a标签**
<a></a>
替换成<router-link></router-link>
组件,配置to属性**,去掉#
- 结合高亮类名实现高亮效果 (推荐模糊匹配:router-link-active)
2. 代码实现
在Layout.vue
文件中进行
…… ……
<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>
…… ……
---------------------------------
<style>
a.router-link-active {
color: orange;
}
</style>
五、首页请求渲染
首页–>嵌套二级路由 面经–>文章列表–> Article.vue
核心步骤
1. 安装 axios
npm install axios
或者yarn add axios
2. 看接口文档,确认请求方式,请求地址,请求参数
请求地址: https://mock.boxuegu.com/mock/3083/articles
请求方式: get
3. created中发请求,获取数据,存起来
async created(){
const res=await axios.get('https://mock.boxuegu.com/mock/3083/articles')
console.log(res);
}
获取数据展示:
代码实现:
<script>
// 请求地址: https://mock.boxuegu.com/mock/3083/articles
// 请求方式: get
import axios from 'axios'
export default {
name: 'ArticlePage',
data () {
return {
articelList: []
}
},
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>
或者 进行解构
data() {
return {
articelList: [],
}
},
async created() { // 解构赋值
const { data: { result: { rows } }} = await axios.get('https://mock.boxuegu.com/mock/3083/articles')
this.articelList = rows
},
4. 页面动态渲染
代码实现:
<template>
<div class="article-page">
<div class="article-item" v-for="item in articelList" :key="item.id">
<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>
六、跳转传参
- 查询参数传参
- ?参数名=参数值
- => this.$route.query.参数名
查询参数传参-代码实现:
在Article.vue
文件中进行
@click="$router.push(`/ArticleDetail?id=${item.id}`)"
在ArticleDetail.vue
文件中进行
created(){
console.log(this.$route.query.id);
}
- 动态路由传参(需要改造路由)
-
/路径/参数
-
this.$route.params.参教名
-
动态路由传参-代码实现:
- 改路由 加上
/:id
在router/index.js
文件中进行
{
path:'/ArticleDetail/:id',
component:ArticleDetail
}
- 在
Article.vue
文件中进行
@click="$router.push(`/ArticleDetail/${item.id}`)"
- 在
ArticleDetail.vue
文件中进行
created(){
console.log(this.$route.params.id);
}
七、修复
-
访问(/)首页
http://localhost:8080/#/
页面出现大片空白原因:没有配置
/
所对应的规则,只对应Layout
的架子,没有匹配任何二级子路由要求:一进页面看到【面经】
解决:重定向
访问首页-代码实现:
在router/index.js
文件中进行
path:'/',
component: Layout,
//增加重定向
redirect:'/Article',
- 面经详情页返回按钮
在ArticleDetail.vue
文件中进行
以下两种都可以:
返回按钮-代码实现:
@click="$router.back()"//返回上一页
@click="$router.push('/article')"//跳转到文章列表
八、详情页渲染
请求地址: https://mock.boxuegu.com/mock/3083/articles/:id
请求方式: get
代码实现:
在ArticleDetail.vue
文件中进行
- 发送请求
<script>
import axios from 'axios'
// 请求地址: https://mock.boxuegu.com/mock/3083/articles/:id
// 请求方式: get
export default {
name: "ArticleDetailPage",
data() {
return {
articleDetail:{}
}
},
async created() {
const id = this.$route.params.id
const {data:{result}} = await axios.get(
`https://mock.boxuegu.com/mock/3083/articles/${id}`
)
this.articleDetail = result
}
}
</script>
- 页面动态渲染
<template>
<div class="article-detail-page">
<nav class="nav">
<span class="back" @click="$router.back()"><</span> 面经详情
</nav>
<header class="header">
<h1>{{articleDetail.stem}}</h1>
<p>{{articleDetail.createAt}} | {{articleDetail.views}} 浏览量 | {{articleDetail.likeCount}} 点赞数</p>
<p>
<img
:src="articleDetail.creatorAvatar"
alt=""
/>
<span>{{articleDetail.creatorName}}</span>
</p>
</header>
<main class="body">
{{articleDetail.content}}
</main>
</div>
</template>
九、组件缓存
-
问题:从【面经】点到【详情页】,又点返回,数据重新加载了→ 希望回到原来的位置
-
原因:路由跳转后,组件被销毁了,返回回来组件又被重建了,所以数据重新被加载了
-
解决:利用 keep-alive 将组件缓存下来
1.keep-alive是什么
keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。(作为容器)
2.keep-alive的优点
在组件切换过程中 把切换出去的组件保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验
<template>
<div class="h5-wrapper">
<keep-alive>
<router-view></router-view>
</keep-alive>
</div>
</template>
演示:
<template>
<div class="h5-wrapper">
<!-- 包裹了keep-alive的两个一级路由组件都会被缓存 -->
<!-- Layout和Article -->
<keep-alive>
<router-view></router-view>
</keep-alive>
</div>
</template>
问题:
点击详情页出去后滑动位置被缓存,但是重新点击另一个详情页,会显示上一个点击的详情页;
即缓存了所有被切换的组件
3.keep-alive的三个属性
① include : 组件名数组,只有匹配的组件会被缓存
② exclude : 组件名数组,任何匹配的组件都不会被缓存
③ max : 最多可以缓存多少组件实例
语法:include / exclude
前不能忘记加上:
:include="['组件name属性']
//eg. :include="['LayoutPage']
4.新增生命周期钩子(了解)
keep-alive的使用会触发两个生命周期函数
-
activated 当组件 被激活(使用) 的时候触发 -> 进入这个页面的时候触发
-
deactivated 当组件 不被使用 的时候触发 -> 离开这个页面的时候触发
-
组件缓存后就不会执行组件的
created,mounted,destroyed
等钩子了,所以其提供了actived
和deactived
钩子,帮我们实现业务需求
原-演示:
在Layout.vue
文件中进行
<script>
export default {
name: "LayoutPage",
created(){
console.log('created 组件被加载了');
},
mounted(){
console.log('mounted dom 渲染完了');
},
destroyed(){
console.log('destroyed 组件被加载了');
}
}
</script>
可以看到组件LayoutPage
缓存了,没有执行其create,mounted,
注:create只在页面第一次加载时执行了,后来再返回首页的时候就没有执行了
新-演示:
十、修复
发现漏了一个小点,访问首页时,默认选择展示面经文章列表,但导航栏没有默认高亮
解决:【两种】
这是第一种:
- 我使用了 Vue 的绑定语法
:class
来动态地添加active
类到当前选中的导航链接上; isPathActive
方法用于检查当前路由路径是否以给定的路径开头。如果匹配,则返回true
,并添加active
类。
代码实现
在Layout.vue
文件中进行
<template>
<div class="h5-wrapper">
<div class="content">
<!-- 内容 -->
<router-view></router-view>
</div>
<nav class="tabbar">
<router-link to="/article" :class="{ active: isPathActive('/Article') }">面经</router-link>
…… ……
</nav>
</div>
</template>
<script>
export default {
name: "LayoutPage",
methods: {
isPathActive(path) {
return this.$route.path.startsWith(path);
}
}
}
</script>
<style>
a.active {
color: orange;
}
</style>
这是第二种:
使用 router-link
的 router-link-active
属性来实现导航栏的高亮显示,而无需编写额外的 isPathActive
方法
router-link-active
属性会自动应用到当前路由激活(即当前被访问的)的 router-link
上
代码实现
在Layout.vue
文件中进行