3.组件插槽
3-1组件插槽
注意
插槽内容可以访问到父组件的数据作用域,因为插槽内容本身就是在父组件模版中定义的
插槽内容无法访问子组件的数据.vue模版中的表达式只能访问其定义时所处的作用域,这和JavaScript的词法作用域是一致的,换言之:
父组件模版的表达式只能访问父组件的作用域,子组件模版中的表达式只能访问子组件的作用域
3-2具名插槽
具名思意:就是给插槽取一个相同的名字,使它到相应的位置
案例
App.vue
<template>
<div>
<!-- 使用具名插槽的使用,使用template进行包裹-->
<!--v-slot:left,记住没有引号-->
<template v-slot:left>
<i>左边</i>
</template>
<!-- 可以省略写成# -->
<template #center>
<div>中间</div>
</template>
<template>
<i>右边</i>
</template>
</div>
</template>
Child.vue
<template>
<div>
<!-- name:是插槽的名字,与父组件的slot要一致 -->
<slot name="left"></slot>
<slot name="center"></slot>
<slot name="right"></slot>
</div>
</template>
3-3作用域插槽
在某些场景下,如果想要同时使用父组件和子组件区域内的数据,要做到这一点,就需要让子组件在渲染的时候,把数据提供给插槽,这就是作用域插槽
案例:通过axios获取猫眼的数据
注意点:
- v-slot=“scope” 不能写在Film组件里面,如果写在Film里面就需要删掉template包装元素
- v-slot=“{scope}”:还可以使用对象解析,因为传过来的是一个对象
App.vue(方式一)
<template>
<Film>
<template v-slot="scope">
<ul>
<li v-for="data in scope.list" :key="data.id">
<img :src="data.img" style="width: 100px;">
{{ data.nm }}
</li>
</ul>
</template>
</Film>
</template>
Film.vue
<template>
<div>
<ul>
<!-- 把数据通过作用域插槽,传到父组件去(可以传多个,传过去的是一个对象-->
<slot :list="dataList">
<li v-for="data in dataList" :key="data.id">
{{ data.nm }}
</li>
</slot>
</ul>
</div>
</template>
<script>
//引入axios模块,需要先下载 npm i axios
import axios from "axios";
export defalut{
data(){
return {
dataList:[]
}
},
//钩子函数,mounted,页面一加载就显示数据
mounted(){
//json文件,记得放在public文件夹下面,否则访问不到
axios.get("../../../public/js/hot.json").then(res => {
console.log(res.data.data.hot);
this.dataList = res.data.data.hot;
})
}
}
</script>
''方式二
具名插槽与作用域联合使用
App.vue
<Film>
<!-- 具名插槽与作用域插槽联合使用 -->
<!--#Film:就是子组件定义了一个name="Film" -->
<!-- v-slot:Film===#Film-->
<!-- <template v-slot:Film="{list,a}"> -->
<!-- 使用default默认插槽,则页面显示的是子组件的内容,不再是父组件的内容 -->
<!-- <template #default="{list,a}"> -->
<template #Film="{list,a}">
{{ a }}
<ul>
<li v-for="data in list" :key="data.id">
<img :src="data.img" style="width: 100px;">
{{ data.nm }}
</li>
</ul>
</template>
</Film>
Film.vue
<!-- 还可以使用具名插槽配合作用域插槽一起使用 -->
<slot :list="dataList" :a="1" :b="2" name="Film">
<ul >
<li v-for="data in dataList" :key="data.id">
{{ data.nm }}
</li>
</ul>
</slot>
4.生命周期
每个Vue组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到DOM,以及在数据改变时更新DOM.在此过程中,它也会运行被称为生命周期钩子函数,让开发者有机会再特定阶段运行自己的代码
(1) beforeCreate()会在实例初始化完成、props 解析之后、data()
和 computed
等选项处理之前立即调用。(无法访问状态以及methods)
(2)created() 当这个钩子被调用时,以下内容已经设置完成:响应式数据、计算属性、方法和侦听器。然而,此时挂载阶段还未开始,因此 $el
属性仍不可用。(能访问到初始化,并且能做初始化)
(3)beforeMount()当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。(第一次创建最后能修改状态的地方)
(4)mounted()所有同步子组件都已经被挂载。这个钩子通常用于执行需要访问组件所渲染的 DOM 树相关的副作用("挂载dom节点,初始化轮播,Echarts,setInterval,ajax)
只要状态改变,5和6函数就会被调用
(5)beforeUpdate()这个钩子可以用来在 Vue 更新 DOM 之前访问 DOM 状态。在这个钩子中更改状态也是安全的。(能访问到最新的状态,无法访问到更新后的dom)
(6)updated()这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的。如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。(访问到更新之后的dom)
(7)beforeUnmount()当这个钩子被调用时,组件实例依然还保有全部的功能。
(8)unmounted()在一个组件实例被卸载之后调用。
Echarts案例
npm i echars
<template>
<!-- 必须要有宽度和高度,否则显示不出来 -->
<div>
<button @click="small">小图</button>
<div id="main" :style="styleObj"></div>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
data() {
return {
styleObj: {
width: "600px",
height: "400px"
}
}
},
methods: {
small() {
this.styleObj.width = "400px"
// this.styleObj.height = "100px"
//----------------方法二,改变大小-----------------
// setTimeout(() => {
// this.myChart.resize()
// },0)
//----------------方法三.直接使用提供的方法-----------------
// 在 Vue 中,DOM 更新不是同步的,而是异步执行的,而 $nextTick 则提供了一种在 DOM 更新后执行代码的方式。
this.$nextTick(() => {
this.myChart.resize()
})
},
},
mounted() {
// 基于准备好的dom,初始化echarts实例
this.myChart = echarts.init(document.getElementById('main'));
// 绘制图表
this.myChart.setOption({
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}
]
});
},
updated() {
// 改方法是控制点击按钮之后,使值生效
// 任何状态更新,都会触发该生命周期函数
// this.myChart.resize();
},
}
</script>
//----------------跟随窗口,大小随着改变-----------------
window.onresize = () => {
this.myChart.resize()
}
},
unmounted() {
// 销毁实例
window.onresize = null
}
挂载在window上的东西,记得要销毁,使用unmound
5.组件的封装
轮播组件
App.vue
<template>
<div>
<MySwiper v-if="dataList.length" @event="handleEvent" :loop="false">
<MySwiperItem v-for="data in dataList" :key="data" >
{{ data.nm }}
<img :src="data.img" alt="" srcset="" style="width: 600px; height:300px" >
</MySwiperItem>
</MySwiper>
{{ info }}
</div>
</template>
<script>
import MySwiper from "./MySwiper.vue";
import MySwiperItem from "./MySwiperItem.vue";
import axios from "axios";
export default {
data() {
return {
dataList: [],
info:""
}
},
methods: {
handleEvent(index) {
console.log(index,);
this.info=this.dataList[index].videoName
}
},
mounted() {
axios.get("../../../public/js/hot.json").then(res => {
console.log(res.data.data.hot);
this.dataList = res.data.data.hot;
this.info=this.dataList[0].videoName
})
},
components: {
MySwiper,
MySwiperItem
}
}
</script>
MySwiper.vue
<template>
<div class="swiper">
<div class="swiper-wrapper">
<slot></slot>
</div>
<!-- If we need pagination -->
<div class="swiper-pagination"></div>
<!-- If we need navigation buttons -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
</template>
<script>
import Swiper from 'swiper/bundle';
import 'swiper/css/bundle';
export default {
props: {
loop: {
type: Boolean,
default: true
}
},
mounted() {
var mySwiper = new Swiper('.swiper', {
loop: this.loop,
// If we need pagination
pagination: {
el: '.swiper-pagination',
},
// Navigation arrows
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
on:{
slideChange: ()=> {
console.log('改变了,activeIndex为' + mySwiper.activeIndex);
this.$emit("event",mySwiper.activeIndex)
},
},
});
}
}
</script>
<style>
.swiper {
width: 600px;
height: 300px;
}
</style>
MySwiperItem.vue
<template>
<div class="swiper-slide">
<slot></slot>
</div>
</template>
6.自定义指令
6-1指令写法&钩子
除了 Vue 内置的一系列指令 (比如 v-model
或 v-show
) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑
全局注册自定义指令
main.js里面
CreateApp(App)
//自定义一个颜色的指令,这里写color就可以,使用指令需要加v-,就是v-color
.directive('color',{
//挂载在mounted上
mounted(el){
//el是dom节点
console.log(el);
el.style.backgroundColor = 'red'
}
})
.mount("#app")
App.vue(局部注册)
<template>
<div>
<!--方式1-->
<h1 v-color="'red'"> </h1>
<!--方式2:动态绑定-->
<h1 v-color="myColor"> </h1>
</div>
</template>
<script>
export defalut{
data(){
return {
myColor:'yellow'
}
}
//自定义指令
directives:{
color:{
mounted(el,binding){
el.style.background=binding.value
},
//动态绑定,需要使用更新钩子函数
updated(el,binding){
el.style.background=binding.value
}
}
}
}
</script>
指令可以简写
对于自定义指令来说,一个很常见的情况是仅仅需要在 mounted
和 updated
上实现相同的行为,除此之外并不需要其他钩子。这种情况下我们可以直接用一个函数来定义指令,如下所示:
<div v-color="color"></div>
color(el, binding) {
el.style.backgroundColor = binding.value;
},
指令钩子
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode, prevVnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {}
}