一、组件插槽
1. 基本使用
// App.vue
<template>
<div class="app">
<show-message title="哈哈哈" content="我是内容哈哈哈"></show-message>
</div>
</template>
<script>
import ShowMessage from './showMessage.vue'
export default {
components: {
ShowMessage
}
}
</script>
// showMessage.vue
<template>
<div class="showMessage">
<h2>{{ title }}</h2>
<div class="content">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ''
},
content: {
type: String,
default: ''
}
}
}
</script>
<style scoped></style>
2. 具名插槽
App.vue
<template>
<div class="app">
<!-- 具名插槽 -->
<nav-bar>
<template v-slot:left>
<button>返回</button>
</template>
<template v-slot:center>
<span>内容</span>
</template>
<!-- v-slot:right 可以简写为 #right -->
<template #right>
<a href="">登录</a>
</template>
</nav-bar>
<!-- 只给一个插槽传递数据,动态插槽名 -->
<nav-bar>
<template v-slot:[position]>
<a href="">注册</a>
</template>
</nav-bar>
<button @click="position = 'left'">左边</button>
<button @click="position = 'center'">中间</button>
<button @click="position = 'right'">右边</button>
</div>
</template>
<script>
import NavBar from './NavBar.vue'
export default {
components: {
NavBar
},
data(){
return {
position: "center"
}
}
}
</script>
<style scoped>
button{
width: 40px;
height: 40px;
}
</style>
NavBar.vue
<template>
<div>
<div class="nav-bar">
<div class="left">
<slot name="left">left</slot>
</div>
<div class="center">
<slot name="center">center</slot>
</div>
<div class="right">
<slot name="right">right</slot>
</div>
</div>
<div class="other">
<!-- 插槽默认名字是default -->
<slot name="default"></slot>
</div>
</div>
</template>
<script>
export default {
props: {},
};
</script>
<style scoped>
.nav-bar {
display: flex;
height: 44px;
text-align: center;
line-height: 44px;
font-size: 16px;
}
.left {
width: 80px;
background-color: orange;
}
.center {
flex: 1;
background-color: skyblue;
}
.right {
width: 80px;
background-color: bisque;
}
</style>
3. 作用域插槽
实现功能:点击tab,激活有红色下划线样式,显示点击的页面
//App.vue
<template>
<div id="app">
<tab-control :titles="['衣服', '鞋子', '裤子']" @tabItemClick="tabItemClick"></tab-control>
<!-- 1.v-slot:default简写为 #default -->
<tab-control :titles="['衣服', '鞋子', '裤子']" @tabItemClick="tabItemClick">
<template #default="props">
<button>{{ props.item }}</button>
</template>
</tab-control>
<!-- 2.独占默认插槽的简写:v-slot:default简写为v-slot -->
<tab-control :titles="['衣服', '鞋子', '裤子']" @tabItemClick="tabItemClick">
<template v-slot="props">
<button>{{ props.item }}</button>
</template>
</tab-control>
<!-- 3.如果只有一个默认插槽,template可以省略,可以直接将v-slot写在组件上 -->
<tab-control :titles="['衣服', '鞋子', '裤子']" @tabItemClick="tabItemClick" v-slot="props">
<button>{{ props.item }}</button>
</tab-control>
<!-- 4.如果有默认插槽和具名插槽就要按照完整的template来编写 -->
<h1>{{ pageContents[currentIndex] }}</h1>
</div>
</template>
<script>
import TabControl from './TabControl.vue'
export default {
components: {
TabControl
},
data() {
return {
pageContents: ["衣服页面", "鞋子页面", "裤子页面"],
currentIndex: 0
}
},
methods: {
tabItemClick(index) {
console.log('app', index);
this.currentIndex = index;
}
}
}
</script>
//TabControl.vue
<template>
<div class="tab-control">
<template v-for="(item, index) in titles">
<div class="tab-control-item" :class="{ active: index === currentIndex }" @click="itemClick(index)">
<slot :item="item">
<span>{{ item }}</span>
</slot>
</div>
</template>
</div>
</template>
<script>
export default {
props: {
titles: {
type: Array,
default: () => []
}
},
data() {
return {
currentIndex: 0
}
},
emits: ["tabItemClick"],
methods: {
itemClick(index) {
this.currentIndex = index;
this.$emit("tabItemClick", index);
}
}
};
</script>
<style scoped>
.tab-control {
display: flex;
height: 44px;
line-height: 44px;
font-size: 16px;
text-align: center;
}
.tab-control-item {
flex: 1;
}
.tab-control-item.active span {
padding: 0 10px;
color: red;
font-weight: 700;
border-bottom: 1px solid red;
}
</style>
二、组件生命周期函数
//App.vue
<template>
<div id="app">
<div>{{message}}</div>
<!-- beforeUpdate updated -->
<button @click="message = 'Hello World'">修改message</button>
<div>
<button @click="isShowHome = !isShowHome">显示Home</button>
<!-- beforeUpdate home-beforeDestroy home-destroyed updated-->
<home v-if="isShowHome"></home>
</div>
</div>
</template>
<script>
import Home from './Home.vue'
export default {
data(){
return {
message: "hello App",
isShowHome: true
}
},
components: {
Home
},
// 1.组件在创建之前
beforeCreate(){
console.log("beforeCreate");
},
// 2.组件被创建完成
created(){
console.log("created");
console.log("1. 发送网络请求,请求数据");
console.log("2. 监听eventBus事件");
console.log("3. 监听watch数据");
},
// 3. 组件template准备被挂载
beforeMount(){
console.log("beforeMount");
},
// 4. 组件template被挂载,虚拟DOM渲染为真实DOM
mounted(){
console.log("mounted");
console.log("1.获取DOM");
console.log("2.使用DOM");
},
// 5.数据发生改变
// 5.1 准备更新DOM
beforeUpdate(){
console.log("beforeUpdate");
},
// 5.2 更新DOM
updated(){
console.log("updated");
},
// 6. 准备卸载VNODE和DOM元素
// 6.1 卸载之前
beforeDestroy(){
console.log("beforeDestroy");
},
// 6.2 卸载完成
destroyed(){
console.log("destroyed");
}
}
</script>
//Home.vue
<template>
<div>Home</div>
</template>
<script>
export default {
name: 'Home',
beforeDestroy(){
console.log("home-beforeDestroy");
},
destroyed(){
console.log("home-destroyed");
}
}
</script>
<style scoped></style>
三、ref获取元素组件
//App.vue
<template>
<div id="app">
<!-- 原生操作 -->
<h2 class="title">Hello World</h2>
<!-- 数据操作 -->
<h2 ref="title">数据和ref获取:{{ message }}</h2>
<button ref="btn" @click="changeTitle">修改title</button>
<banner ref="banner"></banner>
</div>
</template>
<script>
import Banner from './Banner.vue'
export default {
data(){
return {
message: "Hello World"
}
},
components: {
Banner
},
methods: {
changeTitle(){
//1. 不推荐操作原生DOM
const titleEl = document.querySelector(".title");
titleEl.textContent = "你好啊,世界";
this.message = "你好啊,世界";
// 2.vue内置属性操作DOM
console.log(this.$refs.title);
console.log(this.$refs.btn);
this.$refs.title.innerText = "你好啊,世界";
// 3.给组件绑定ref,拿到的就是组件实例
console.log(this.$refs.banner);
// 3.1父组件可以调用子组件的方法
this.$refs.banner.bannerClick();
// 3.2 获取banner组件实例,获取banner中的元素
console.log(this.$refs.banner.$el);
// 3.3 如果banner template有多个根节点,拿到的是另一个node节点
// 注意:开发中不推荐有多个根
// console.log(this.$refs.banner.$el.nextElementSibling);
// 4.组件实例还有两个属性(了解)
console.log(this.$parent);
console.log(this.$root);
// 5.this.$children在vue3中移除
}
}
}
</script>
//Banner.vue
<template>
<div class="banner">
<h2>banner子组件</h2>
</div>
</template>
<script>
export default {
name: 'banner',
methods: {
bannerClick(){
console.log("banner");
}
}
}
</script>
<style scoped></style>
四、动态组件
//App.vue
<template>
<div id="app">
<div class="tabs">
<template v-for="(item, index) in tabs">
<button @click="itemClick(item)" :class="{ active: currentTab === item }">
<!-- :class="{ active: currentIndex === index }" -->
{{ item }}
</button>
</template>
</div>
<div class="view">
<!-- 1. 第一种做法:v-if进行判断逻辑,决定显示那个组件 -->
<!-- <template v-if="currentIndex === 0">
<home></home>
</template>
<template v-if="currentIndex === 1">
<about></about>
</template>
<template v-if="currentIndex === 2">
<category></category>
</template> -->
<!-- 2. 动态组件 -->
<!-- is中的组件需要来自两个地方:1.全局注册的组件 2.局部注册的组件 -->
<!-- <component :is="tabs[currentIndex]"></component> -->
<component name="wx" :age="18" @homeClick="homeClick" :is="currentTab"></component>
</div>
</div>
</template>
<script>
import Home from "./views/Home.vue";
import About from "./views/About.vue";
import Category from "./views/Category.vue";
export default {
data() {
return {
tabs: ["home", "about", "category"],
currentTab: "home",
// currentIndex: 0
};
},
components: {
Home,
About,
Category,
},
methods: {
itemClick(tab) {
// this.currentIndex = index;
this.currentTab = tab;
},
homeClick(arg) {
console.log("homeClick", arg);
},
},
};
</script>
<style scoped>
.active {
color: red;
}
</style>
//Home.vue
<template>
<div class="home">
<h2>home组件:{{ name }} - {{ age }}</h2>
<button @click="homeBtnClick">homeBtn</button>
</div>
</template>
<script>
export default {
name: 'home',
props: {
name: {
type: String,
default: ""
},
age: {
type: Number,
default: 0
}
},
methods: {
homeBtnClick(){
this.$emit("homeClick", "home");
}
}
}
</script>
<style scoped></style>
//About.vue
<template>
<div class="about">
<h2>about</h2>
</div>
</template>
<script>
export default {
name: 'about'
}
</script>
<style scoped></style>
//Category.vue
<template>
<div class="category">
<h2>category</h2>
</div>
</template>
<script>
export default {
name: 'category'
}
</script>
<style scoped></style>
五、keep-Alive的使用
缓存了的组件不会销毁,keep-alive生命周期,进入组件的时候activated,离开组件的时候deactivated
//App.vue
<template>
<div id="app">
<div class="tabs">
<template v-for="(item, index) in tabs">
<button
@click="itemClick(item)"
:class="{ active: currentTab === item }"
>
{{ item }}
</button>
</template>
</div>
<div class="view">
<!-- 1.全部缓存 -->
<!-- <keep-alive >
<component :is="currentTab"></component>
</keep-alive> -->
<!--
include - string | RegExp | Array: 只有名称匹配的组件会被缓存
exclude - string | RegExp | Array:任何名称匹配的组件都不会被缓存
max - number | string:最多可以缓存多少个组件实例,一旦达到这个数字,那么缓存组件中最近没有被访问的实例会被销毁
-->
<!--
include和exclude 属性允许组件有条件的缓存:
2.1 include="home,about"表示只缓存home,about组件
2.2 匹配首先检查组件自身的name选项,不是注册的名字
2.3 二者都可以用逗号分隔字符串、正则表达式或一个数组来表示
include="home,about"
:include="/home|about/"
:include="['home', 'about']"
-->
<keep-alive include="home,about">
<component :is="currentTab"></component>
</keep-alive>
</div>
</div>
</template>
<script>
import Home from "./views/Home.vue";
import About from "./views/About.vue";
import Category from "./views/Category.vue";
export default {
data() {
return {
tabs: ["home", "about", "category"],
currentTab: "home",
// currentIndex: 0
};
},
components: {
Home,
About,
Category,
},
methods: {
itemClick(tab) {
// this.currentIndex = index;
this.currentTab = tab;
}
},
};
</script>
<style scoped>
.active {
color: red;
}
</style>
//Home.vue
<template>
<div class="home">
<h2>home组件</h2>
<h2>当前计数:{{ counter }}</h2>
<button @click="counter++">+1</button>
</div>
</template>
<script>
export default {
name: 'home',
data(){
return {
counter: 0
}
},
created(){
console.log("home created");
},
destroyed(){
console.log("home destroyed");
},
// 对于缓存组件来说,再次进入时,是不会执行created或者mounted等生命周期函数的
// keep-alive组件进入活跃状态
activated(){
console.log("activated");
},
deactivated(){
console.log("deactivated");
}
}
</script>
<style scoped></style>
//About.vue
<template>
<div class="about">
<h2>about</h2>
</div>
</template>
<script>
export default {
name: 'about',
destroyed(){
console.log("about destroyed");
}
}
</script>
<style scoped></style>
//Category.vue
<template>
<div class="category">
<h2>category</h2>
</div>
</template>
<script>
export default {
name: 'category',
destroyed(){
console.log("category destroyed");
}
}
</script>
<style scoped></style>
六、异步组件的使用
//App.vue
<template>
<div id="app">
<div class="tabs">
<template v-for="(item, index) in tabs">
<button @click="itemClick(item)" :class="{ active: currentTab === item }">
{{ item }}
</button>
</template>
</div>
<div class="view">
<keep-alive include="home,about">
<component :is="currentTab"></component>
</keep-alive>
</div>
</div>
</template>
<script>
import { defineAsyncComponent } from 'vue'
import Home from "./views/Home.vue";
import About from "./views/About.vue";
// import Category from "./views/Category.vue";
/**
* webpack分包:用import(...)函数引入的文件在打包的时候会独立打包
* + 返回一个promise对象
*
* 要想对组件分包
* + components中需要放一个组件
* + 可以用vue自带的defineAsyncComponent定义一个组件
* + 会对这个组件进行分包处理,打包后另加一个JS
*/
const Category = defineAsyncComponent(() => import("./views/Category.vue"))
export default {
data() {
return {
tabs: ["home", "about", "category"],
currentTab: "home"
};
},
components: {
Home,
About,
Category,
},
methods: {
itemClick(tab) {
this.currentTab = tab;
}
},
};
</script>
<style scoped>
.active {
color: red;
}
</style>
其余页面是keep-alive中的
七、组件的v-model
v-model数据的双向绑定:
组件的v-model 自定义多个属性名称:
//App.vue
<template>
<div class="app">
<!-- 1.input v-model数据的双向绑定 -->
<!-- <input v-model="message" />
<input :value="message" @input="message = $event.target.value" /> -->
<!-- 2.vue3组件的v-model-->
<!-- <counter v-model="appCounter"></counter> -->
<!-- vue2组件 v-model 实现-->
<counter :value="appCounter" @input="appCounter = $event"></counter>
<!-- vue3组件 v-model 实现-->
<!-- <counter :modelValue="appCounter" @update:modelValue="appCounter = $event"></counter> -->
<!-- 3.组件的v-model 自定义多个属性名称 counter name-->
<counter2
v-model:counter="appCounter"
@update:counter="appCounter = $event"
v-model:name="appName"
></counter2>
</div>
</template>
<script>
import Counter from './Counter.vue'
import Counter2 from './Counter2.vue'
export default {
components: {
Counter,
Counter2
},
data() {
return {
appCounter: 100,
message: '',
appName: 'wx'
}
}
}
</script>
//Counter.vue
<template>
<div class="counter">
<!--
+ 在vue2中一个组件上的v-model默认会利用名为value的props和名为input的事件
+ vue3在组件上的用法发生了变化:
+ value-> modelValue
+ input -> update:modelValue
-->
<h2>Counter: {{ value }}</h2>
<button @click="changeCounter">修改counter</button>
</div>
</template>
<script>
export default{
props: {
modelValue: {
type: Number,
default: 0
},
value: {
type: Number,
default: 0
}
},
methods: {
changeCounter(){
// this.$emit("update:modelValue", 99)
this.$emit("input", 99);
}
}
}
</script>
//Counter2.vue
<template>
<div class="counter">
<!--
+ 在vue2中一个组件上的v-model默认会利用名为value的props和名为input的事件
+ vue3在组件上的用法发生了变化:
+ value-> modelValue
+ input -> update:modelValue
-->
<h2>Counter: {{ counter }}</h2>
<button @click="changeCounter">修改counter</button>
<!-- name绑定 绑定多个值 -->
<h2>name: {{ name }}</h2>
<button @click="changeName">修改name</button>
</div>
</template>
<script>
export default{
props: {
counter: {
type: Number,
default: 0
},
name: {
type: String,
default: ''
}
},
emits: ["update:counter"],
methods: {
changeCounter(){
this.$emit("update:counter", 99);
},
changeName(){
this.$emit("update:name", "Hezi");
}
}
}
</script>