一、ref
1.ref引用
每个vue组件实例上,都包含一个$refs对象,里面存储着对应dom元素或组件的引用。默认情况下,组件的$refs指向一个空对象。
2.使用ref获取dom元素的引用
<template>
<h3 ref="myh3">ref组件</h3>
<button @click="getRef">getRef</button>
</template>
<script>
export default{
methods:{
getRef(){
console.log('this',this.$refs);
this.$refs.myh3.style.color='red'
}
}
}
</script>
3.使用ref引用组件实例
<template>
<MyIndex ref="myIndexRef"></MyIndex>
<button @click="getRef">getRef</button>
</template>
<script>
import MyIndex from './index copy.vue';
export default{
components:{MyIndex},
methods:{
getRef(){
console.log('this',this.$refs);
this.$refs.myIndexRef.reset()
}
}
}
</script>
<template>
<div>
{{count}}
<button @click="this.count+=1">按钮</button>
</div>
</template>
<script>
export default{
data(){
return{
count:1
}
},
methods:{
reset(){
this.count=0
}
}
}
</script>
4.控制文本框和按钮的按需切换
<template>
<input type="text" v-if="inputVisible" ref="ipt">
<button v-else @click="showInput">getRef</button>
</template>
<script>
export default{
data(){
return{
inputVisible:false
}
},
methods:{
showInput(){
this.inputVisible=true;
//获取文本框的引用对象,访问的ipt是undefind,dom更新是异步的,拿不到最新dom
this.$nextTick(()=>{
this.$refs.ipt.focus();
})
}
}
}
</script>
5.组件是异步执行dom更新的
6.this.$nextTick(cb)
组件的$nextTick(cb)方法,会把cb回调推迟到下一个dom更新周期之后执行。简单理解:等组件的dom异步地重新渲染完成后,再执行cb回调函数。从而能保证cb回调函数可以操作到最新的dom元素。
二、动态组件
1.什么是动态组件
动态组件时动态切换组建的显示与隐藏。vue提供了一个<component>组件,专门用来实现组件的动态渲染。
a.<component>是组件的占位符
b.通过is属性动态指定要渲染的组件名称
c.<component is="要渲染的组件名称"></component>
2.如何实现动态组件渲染
<template>
<button @click="comName='Home'">首页</button>
<button @click="comName='Movie'">电影</button>
<hr>
<component :is='comName'></component>
</template>
<script>
import Home from './home.vue'
import Movie from './movie.vue'
export default{
components:{Home,Movie},
data(){
return{
comName:'Home'
}
}
}
</script>
3.使用keep-alive保持组件的状态
默认情况下,动态切换组件时无法保持组件的状态。可以使用vue内置的<keep-alive>组件保持动态组件的状态。
<template>
<button @click="comName='Home'">首页</button>
<button @click="comName='Movie'">电影</button>
<hr>
<keep-alive>
<component :is='comName'></component>
</keep-alive>
</template>
<script>
import Home from './home.vue'
import Movie from './movie.vue'
export default{
components:{Home,Movie},
data(){
return{
comName:'Home'
}
}
}
</script>
<template>
<div>Home组件{{ count }}
<button @click="count+=1">+1</button>
</div>
</template>
<script>
export default{
data(){
return{
count:1
}
},
created(){
console.log('created');
},
unmounted(){
console.log('unmounted');
},
}
</script>
三、插槽
1.什么是插槽
插槽是vue组件为组件的封装者提供的能力。允许开发者在封装组件时,把不确定、希望由用户指定的部分定义为插槽。
可以把插槽认为是组件封装期间,为用户预留的内容占位符。
没有预留插槽的内容会被丢弃
<template>
<Home>
<p>-------------</p>
</Home>
</template>
<script>
import Home from './home.vue'
export default{
components:{Home},
}
</script>
<template>
<div>Home组件
<p>p1p1p1p1p1p1p1p1p1p1p1p1p1</p>
<slot></slot>
<p>p2p2p2p2p2p2p2p2p2p2p2p2p2</p>
</div>
</template>
2.后备内容(默认内容)
封装组件时,可以为预留的<slot>插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何内容,则后备内容会生效。
<template>
<Home>
<!-- <p>-------------</p> -->
</Home>
</template>
<script>
import Home from './home.vue'
export default{
components:{Home},
}
</script>
<template>
<div>Home组件
<p>p1p1p1p1p1p1p1p1p1p1p1p1p1</p>
<slot>默认xxxxxxxx</slot>
<p>p2p2p2p2p2p2p2p2p2p2p2p2p2</p>
</div>
</template>
3.具名插槽
如果在封装组件时需要预留多个插槽节点,则需要为每个<slot>插槽指定具体的name名称。这种带有具体名称的插槽叫做“具名插槽”。
v-slot:可以简写为字符#
<template>
<Home>
<!-- v-slot:可简写为# -->
<template v-slot:header>头部</template>
<p>-------------</p>
<template #footer>底部</template>
</Home>
</template>
<script>
import Home from './home.vue'
export default{
components:{Home},
}
</script>
<template>
<div>Home组件
<slot name="header"></slot>
<slot>默认xxxxxxxx</slot>
<slot name="footer"></slot>
</div>
</template>
4.作用域插槽
在封装组件的过程中,可以为预留的<slot>插槽绑定props数据,这种带有props数据的<slot>插槽叫做作用域插槽。
<template>
<Home>
<template v-slot:default="scope">{{ scope }}{{ scope.info.name }}</template>
</Home>
</template>
<script>
import Home from './home.vue'
export default{
components:{Home},
}
</script>
<template>
<div>Home组件
<slot :info="infomation" :msg="msg"></slot>
</div>
</template>
<script>
export default{
data(){
return{
infomation:{
name:'zs',
age:18
},
msg:'123'
}
}
}
</script>
解构作用域插槽
<template>
<Home>
<template #default="{info,msg}">{{ info.name }}{{ info.age }}{{ msg }}</template>
</Home>
</template>
<script>
import Home from './home.vue'
export default{
components:{Home},
}
</script>
作用域插槽的使用场景
<template>
<Home>
<template #default="{user}">
<td>{{user.name}}</td>
<td>{{ user.age }}</td>
<td>
<input type="checkbox" :checked="user.state">
</td>
</template>
</Home>
</template>
<script>
import Home from './home.vue'
export default{
components:{Home},
}
</script>
<template>
<div>Home组件
<table>
<tr>
<td>名字</td>
<td>年龄</td>
<td>状态</td>
</tr>
<tr v-for="item in list" :key="item">
<slot :user="item"></slot>
</tr>
</table>
</div>
</template>
<script>
export default{
data(){
return{
list:[{
name:'zs1',
age:18,
state:true
},
{
name:'zs2',
age:17,
state:false
},{
name:'zs3',
age:15,
state:true
}],
}
}
}
</script>
四、自定义指令
vue 中自定义指令分为两类:私有自定义指令,全局自定义指令。
在每个vue组件中,可以在directives节点下声明私有自定义指令。
1.私有自定义指令
<template>
<input type="text" v-focus>
</template>
<script>
export default{
directives:{
focus:{
//当被绑定的元素插入到dom中时,会自定触发mounted函数
mounted(el){
el.focus()//让被绑定的元素自动获取焦点
}
}
}
}
</script>
2.全局自定义指令
在main.js文件中声明自定义指令
const app = createApp(App)
app.directive('focus',{
mounted(el){
el.focus()
}
})
3.updated函数
mounted函数只在元素第一次插入dom时被调用,当dom更新时mounted函数不会被触发。updated函数会在每次dom更新后被调用。
<template>
<input type="text" v-focus>
{{ count }}
<button @click="count+=1">+1</button>
</template>
<script>
export default{
data(){
return{
count:1
}
}
}
</script>
//main.jsmain.js
const app = createApp(App)
app.directive('focus',{
mounted(el){
el.focus()
},
updated(el){
el.focus()
}
})
在vue2的项目中使用自定义指令时,mounted->bind,updated->update
如果mounted,updated函数中的逻辑完全相同,可以简写
const app = createApp(App)
app.directive('focus',(el)=>{
el.focus()
})
4.指令的参数值
在绑定指令时,可以通过等号的形式为指令绑定具体的参数值。binding.value
<template>
<input type="text" v-model.number="count" v-focus v-color="'red'">
<p v-color="'green'">{{ count }}</p>
<button @click="count+=1">+1</button>
</template>
<script>
import Home from './home.vue'
export default{
components:{Home},
data(){
return{
count:1
}
}
}
</script>
//main.js
const app = createApp(App)
app.directive('focus',(el)=>{
el.focus()
})
app.directive('color',(el,binding)=>{
el.style.color=binding.value
})
五、Table案例
1.搭建项目基本结构
npm create vite
cd vite-project
npm install
2.请求商品列表数据
npm i axios
//main.js
import axios from 'axios'
//配置请求的根路径
axios.defaults.baseURL='http://localhost:3000'
//将axios挂载为全局的$http自定义属性
app.config.globalProperties.$http=axios
3.封装MyTable组件
a.通过props属性,为MyTable.vue组件指定数据源
b.在MyTable.vue组件中,预留header的具名插槽
c.在MyTable.vue组件中,预留body的作用域插槽
<template>
<table>
<thead>
<slot name="header"></slot>
</thead>
<tbody>
<tr v-for="(item,index) in data" :key="item">
<slot name="body" :row="item" :index="index"></slot>
</tr>
</tbody>
</table>
</template>
<script>
export default{
props:{
data:{
type:Array,
default:[],
require:true
}
}
}
</script>
4.实现删除功能
5.实现添加标签的功能
a.实现input和button的按需展示(v-if,v-else)
b.让input自动获取焦点(自定义指令)
c.文本框失去焦点自动隐藏
d.文本框的enter,esc按键事件
把用户在文本框中输入的值,预先转存到常量val中,清空文本的值,v-if='false'隐藏文本框
判断val的值是否为空,如果为空,则不进行添加,
判断val的值是否已经存在于数组中,如果存在,则不进行添加,return
将用户输入的内容,作为新标签push到当前tag数组中
<template>
<MyTable :data="goodsList">
<template #header>
<td>#</td>
<td>商品名称</td>
<td>价格</td>
<td>标签</td>
<td>操作</td>
</template>
<template #body="{row,index}">
<td>{{index+1}}</td>
<td>{{ row.title }}</td>
<td>¥{{ row.price }}</td>
<td>
<input
v-if="row.inputVisible"
ref="InputRef"
v-model.trim="row.inputValue"
class="ml-1 w-20"
@keyup.enter="handleInputConfirm(row)"
@blur="handleInputConfirm(row)"
@keyup.esc="row.inputValue=''"
v-focus
/>
<el-button v-else class="button-new-tag ml-1" size="small" @click="row.inputVisible = true">
+Tag
</el-button>
<el-tag class="ml-2" type="success" v-for="item in row.tag" :key="item">{{ item }}</el-tag></td>
<td>
<el-button type="danger" @click="deleteFn(row.id)">删除</el-button>
</td>
</template>
</MyTable>
</template>
<script>
import MyTable from './MyTable.vue'
export default{
components:{MyTable},
data(){
return{
goodsList:[]
}
},
methods:{
async getGoodsList(){
const {data:res}=await this.$http.get('/goodsList')
console.log('res',res);
this.goodsList = res.map(item=>{
return{
...item,
inputVisible:false,
inputValue:''
}
})
},
deleteFn(id){
this.goodsList = this.goodsList.filter(item=>item.id!==id)
},
handleInputConfirm(row){
const val = row.inputValue;
row.inputValue = '';
row.inputVisible = false;
if(!val||row.tag.indexOf(val) !==-1)return
row.tag.push(val)
},
},
created(){
this.getGoodsList()
},
directives:{
focus(el){
el.focus()
}
}
}
</script>