一、学习目标
1.自定义指令
- 基本语法(全局、局部注册)
- 指令的值
- v-loading的指令封装
2.插槽
- 默认插槽
- 具名插槽
- 作用域插槽
3.综合案例:商品列表
- MyTag组件封装
- MyTable组件封装
4.路由入门
- 单页应用程序
- 路由
- VueRouter的基本使用
一、自定义指令
1.指令介绍
-
内置指令:v-html、v-if、v-bind、v-on… 这都是Vue给咱们内置的一些指令,可以直接使用
-
自定义指令:同时Vue也支持让开发者,自己注册一些指令。这些指令被称为自定义指令
每个指令都有自己各自独立的功能
2.自定义指令
概念:自己定义的指令,可以封装一些DOM操作,扩展额外的功能
3.自定义指令语法
-
全局注册
//在main.js中 Vue.directive('指令名', { "inserted" (el) { // 可以对 el 标签,扩展额外功能 el.focus() } })
-
局部注册
//在Vue组件的配置项中 directives: { "指令名": { inserted () { // 可以对 el 标签,扩展额外功能 el.focus() } } }
-
使用指令
注意:在使用指令的时候,一定要先注册,再使用,否则会报错
使用指令语法: v-指令名。如:注册指令时不用加v-前缀,但使用时一定要加v-前缀
4.指令中的配置项介绍
inserted:被绑定元素插入父节点时调用的钩子函数
el:使用指令的那个DOM元素
5.代码示例
需求:当页面加载时,让元素获取焦点(autofocus在safari浏览器有兼容性)
App.vue
<div>
<h1>自定义指令</h1>
<input v-focus ref="inp" type="text">
</div>
二、自定义指令-指令的值
1.需求
实现一个 color 指令 - 传入不同的颜色, 给标签设置文字颜色
2.语法
1.在绑定指令时,可以通过“等号”的形式为指令 绑定 具体的参数值
<div v-color="color">我是内容</div>
2.通过 binding.value 可以拿到指令值,指令值修改会 触发 update 函数
directives: {
color: {
inserted (el, binding) {
el.style.color = binding.value
},
update (el, binding) {
el.style.color = binding.value
}
}
}
3.代码示例
App.vue
<template>
<div>
<!--显示红色-->
<h2 v-color="color1">指令的值1测试</h2>
<!--显示蓝色-->
<h2 v-color="color2">指令的值2测试</h2>
<button>
改变第一个h1的颜色
</button>
</div>
</template>
<script>
export default {
data () {
return {
color1: 'red',
color2: 'blue'
}
}
}
</script>
<style>
</style>
三、自定义指令-v-loading指令的封装
1.场景
实际开发过程中,发送请求需要时间,在请求的数据未回来时,页面会处于空白状态 => 用户体验不好
2.需求
封装一个 v-loading 指令,实现加载中的效果
3.分析
1.本质 loading效果就是一个蒙层,盖在了盒子上
2.数据请求中,开启loading状态,添加蒙层
3.数据请求完毕,关闭loading状态,移除蒙层
4.实现
1.准备一个 loading类,通过伪元素定位,设置宽高,实现蒙层
2.开启关闭 loading状态(添加移除蒙层),本质只需要添加移除类即可
3.结合自定义指令的语法进行封装复用
.loading:before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff url("./loading.gif") no-repeat center;
}
5.准备代码
<template>
<div class="main">
<div class="box">
<ul>
<li v-for="item in list" :key="item.id" class="news">
<div class="left">
<div class="title">{{ item.title }}</div>
<div class="info">
<span>{{ item.source }}</span>
<span>{{ item.time }}</span>
</div>
</div>
<div class="right">
<img :src="item.img" alt="">
</div>
</li>
</ul>
</div>
</div>
</template>
<script>
// 安装axios => yarn add axios || npm i axios
import axios from 'axios'
// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
export default {
data () {
return {
list: [],
isLoading: false,
isLoading2: false
}
},
async created () {
// 1. 发送请求获取数据
const res = await axios.get('http://hmajax.itheima.net/api/news')
setTimeout(() => {
// 2. 更新到 list 中,用于页面渲染 v-for
this.list = res.data.data
}, 2000)
}
}
</script>
<style>
.loading:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff url('./loading.gif') no-repeat center;
}
.box2 {
width: 400px;
height: 400px;
border: 2px solid #000;
position: relative;
}
.box {
width: 800px;
min-height: 500px;
border: 3px solid orange;
border-radius: 5px;
position: relative;
}
.news {
display: flex;
height: 120px;
width: 600px;
margin: 0 auto;
padding: 20px 0;
cursor: pointer;
}
.news .left {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-right: 10px;
}
.news .left .title {
font-size: 20px;
}
.news .left .info {
color: #999999;
}
.news .left .info span {
margin-right: 20px;
}
.news .right {
width: 160px;
height: 120px;
}
.news .right img {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
四、插槽-默认插槽
1.作用
让组件内部的一些 结构 支持 自定义
2.需求
将需要多次显示的对话框,封装成一个组件
3.问题
组件的内容部分,不希望写死,希望能使用的时候自定义。怎么办
4.插槽的基本语法
- 组件内需要定制的结构部分,改用****占位
- 使用组件时, ****标签内部, 传入结构替换slot
- 给插槽传入内容时,可以传入纯文本、html标签、组件
5.代码示例
MyDialog.vue
<template>
<div class="dialog">
<div class="dialog-header">
<h3>友情提示</h3>
<span class="close">✖️</span>
</div>
<div class="dialog-content">
您确定要进行删除操作吗?
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
</template>
<script>
export default {
data () {
return {
}
}
}
</script>
<style scoped>
* {
margin: 0;
padding: 0;
}
.dialog {
width: 470px;
height: 230px;
padding: 0 25px;
background-color: #ffffff;
margin: 40px auto;
border-radius: 5px;
}
.dialog-header {
height: 70px;
line-height: 70px;
font-size: 20px;
border-bottom: 1px solid #ccc;
position: relative;
}
.dialog-header .close {
position: absolute;
right: 0px;
top: 0px;
cursor: pointer;
}
.dialog-content {
height: 80px;
font-size: 18px;
padding: 15px 0;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
}
.dialog-footer button {
width: 65px;
height: 35px;
background-color: #ffffff;
border: 1px solid #e1e3e9;
cursor: pointer;
outline: none;
margin-left: 10px;
border-radius: 3px;
}
.dialog-footer button:last-child {
background-color: #007acc;
color: #fff;
}
</style>
App.vue
<template>
<div>
<MyDialog>
</MyDialog>
</div>
</template>
<script>
import MyDialog from './components/MyDialog.vue'
export default {
data () {
return {
}
},
components: {
MyDialog
}
}
</script>
<style>
body {
background-color: #b3b3b3;
}
</style>
五、插槽-后备内容(默认值)
1.问题
通过插槽完成了内容的定制,传什么显示什么, 但是如果不传,则是空白
能否给插槽设置 默认显示内容 呢?
2.插槽的后备内容
封装组件时,可以为预留的 <slot>
插槽提供后备内容(默认内容)。
3.语法
在 标签内,放置内容, 作为默认显示内容
4.效果
-
外部使用组件时,不传东西,则slot会显示后备内容
-
外部使用组件时,传东西了,则slot整体会被换掉
5.代码示例
App.vue
<template>
<div>
<MyDialog></MyDialog>
<MyDialog>
你确认要退出么
</MyDialog>
</div>
</template>
<script>
import MyDialog from './components/MyDialog.vue'
export default {
data () {
return {
}
},
components: {
MyDialog
}
}
</script>
<style>
body {
background-color: #b3b3b3;
}
</style>
六、插槽-具名插槽
1.需求
一个组件内有多处结构,需要外部传入标签,进行定制
上面的弹框中有三处不同,但是默认插槽只能定制一个位置,这时候怎么办呢?
2.具名插槽语法
-
多个slot使用name属性区分名字
-
template配合v-slot:名字来分发对应标签
3.v-slot的简写
v-slot写起来太长,vue给我们提供一个简单写法 v-slot —> #
4.总结
- 组件内 有多处不确定的结构 怎么办?
- 具名插槽的语法是什么?
- v-slot:插槽名可以简化成什么?
七、作用域插槽
1.插槽分类
-
默认插槽
-
具名插槽
插槽只有两种,作用域插槽不属于插槽的一种分类
2.作用
定义slot 插槽的同时, 是可以传值的。给 插槽 上可以 绑定数据,将来 使用组件时可以用
3.场景
封装表格组件
4.使用步骤
-
给 slot 标签, 以 添加属性的方式传值
<slot :id="item.id" msg="测试文本"></slot>
-
所有添加的属性, 都会被收集到一个对象中
{ id: 3, msg: '测试文本' }
-
在template中, 通过
#插槽名= "obj"
接收,默认插槽名为 default<MyTable :list="list"> <template #default="obj"> <button @click="del(obj.id)">删除</button> </template> </MyTable>
5.代码示例
MyTable.vue
<template>
<table class="my-table">
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年纪</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>赵小云</td>
<td>19</td>
<td>
<button>
查看
</button>
</td>
</tr>
<tr>
<td>1</td>
<td>张小花</td>
<td>19</td>
<td>
<button>
查看
</button>
</td>
</tr>
<tr>
<td>1</td>
<td>孙大明</td>
<td>19</td>
<td>
<button>
查看
</button>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props: {
data: Array
}
}
</script>
<style scoped>
.my-table {
width: 450px;
text-align: center;
border: 1px solid #ccc;
font-size: 24px;
margin: 30px auto;
}
.my-table thead {
background-color: #1f74ff;
color: #fff;
}
.my-table thead th {
font-weight: normal;
}
.my-table thead tr {
line-height: 40px;
}
.my-table th,
.my-table td {
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
}
.my-table td:last-child {
border-right: none;
}
.my-table tr:last-child td {
border-bottom: none;
}
.my-table button {
width: 65px;
height: 35px;
font-size: 18px;
border: 1px solid #ccc;
outline: none;
border-radius: 3px;
cursor: pointer;
background-color: #ffffff;
margin-left: 5px;
}
</style>
App.vue
<template>
<div>
<MyTable :data="list"></MyTable>
<MyTable :data="list2"></MyTable>
</div>
</template>
<script>
import MyTable from './components/MyTable.vue'
export default {
data () {
return {
list: [
{ id: 1, name: '张小花', age: 18 },
{ id: 2, name: '孙大明', age: 19 },
{ id: 3, name: '刘德忠', age: 17 },
],
list2: [
{ id: 1, name: '赵小云', age: 18 },
{ id: 2, name: '刘蓓蓓', age: 19 },
{ id: 3, name: '姜肖泰', age: 17 },
]
}
},
components: {
MyTable
}
}
</script>
八、单页应用程序介绍
1.概念
单页应用程序:SPA【Single Page Application】是指所有的功能都在一个html页面上实现
2.具体示例
单页应用网站: 网易云音乐 https://music.163.com/
多页应用网站:京东 https://jd.com/
3.单页应用 VS 多页面应用
单页应用类网站:系统类网站 / 内部网站 / 文档类网站 / 移动端站点
多页应用类网站:公司官网 / 电商类网站
九、路由介绍
1.思考
单页面应用程序,之所以开发效率高,性能好,用户体验好
最大的原因就是:页面按需更新
比如当点击【发现音乐】和【关注】时,只是更新下面部分内容,对于头部是不更新的
要按需更新,首先就需要明确:访问路径和 组件的对应关系!
访问路径 和 组件的对应关系如何确定呢? 路由
2.路由的介绍
生活中的路由:设备和ip的映射关系
Vue中的路由:路径和组件的映射关系
十、路由的基本使用
1.目标
认识插件 VueRouter,掌握 VueRouter 的基本使用步骤
2.作用
修改地址栏路径时,切换显示匹配的组件
3.说明
Vue 官方的一个路由插件,是一个第三方包
4.官网
https://v3.router.vuejs.org/zh/
5.VueRouter的使用(5+2)
固定5个固定的步骤(不用死背,熟能生巧)
-
下载 VueRouter 模块到当前工程,版本3.6.5
yarn add vue-router@3.6.5
-
main.js中引入VueRouter
import VueRouter from 'vue-router'
-
安装注册
Vue.use(VueRouter)
-
创建路由对象
const router = new VueRouter()
-
注入,将路由对象注入到new Vue实例中,建立关联
new Vue({ render: h => h(App), router:router }).$mount('#app')
当我们配置完以上5步之后 就可以看到浏览器地址栏中的路由 变成了 /#/的形式。表示项目的路由已经被Vue-Router管理了
6.代码示例
main.js
// 路由的使用步骤 5 + 2
// 5个基础步骤
// 1. 下载 v3.6.5
// yarn add vue-router@3.6.5
// 2. 引入
// 3. 安装注册 Vue.use(Vue插件)
// 4. 创建路由对象
// 5. 注入到new Vue中,建立关联
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化
const router = new VueRouter()
new Vue({
render: h => h(App),
router
}).$mount('#app')
7.两个核心步骤
-
创建需要的组件 (views目录),配置路由规则
-
配置导航,配置路由出口(路径匹配的组件显示的位置)
App.vue
<div class="footer_wrap">
<a href="#/find">发现音乐</a>
<a href="#/my">我的音乐</a>
<a href="#/friend">朋友</a>
</div>
<div class="top">
<router-view></router-view>
</div>
十一、组件的存放目录问题
注意: .vue文件 本质无区别
1.组件分类
.vue文件分为2类,都是 .vue文件(本质无区别)
- 页面组件 (配置路由规则时使用的组件)
- 复用组件(多个组件中都使用到的组件)
2.存放目录
分类开来的目的就是为了 更易维护
-
src/views文件夹
页面组件 - 页面展示 - 配合路由用
-
src/components文件夹
复用组件 - 展示数据 - 常用于复用
十二、路由的封装抽离
问题:所有的路由配置都在main.js中合适吗?
目标:将路由模块抽离出来。 好处:拆分模块,利于维护
路径简写:
脚手架环境下 @指代src目录,可以用于快速引入组件