1.ref 引用
ref 用来辅助开发者在不依赖 jQuery 的情况下,获取DOM元素或组件的引用。
1.1 每个vue的组件实例上,都包含一个$refs
对象,里面存储着对应的DOM元素或组件的引用。默认情况下,组件的$refs
指向一个空对象。
1.2 使用ref引用页面上的DOM元素和引用页面上的组件实例:
<template>
<div>
<!-- 1、使用ref属性,为对应的DOM添加引用名称 -->
App 根组件
<button type="button" class="btn btn-primary" @click="getRefs">获取 $refs 引用</button>
<!-- 3、使用ref属性,为组件添加引用名称 -->
<my-counter ref="counterRef"></my-counter>
</div>
</template>
<script>
// 导入组件
import MyCounter from './Counter.vue'
export default {
name: 'MyApp',
methods: {
getRefs() {
// this代表当前组件的实例对象,this.$refs默认指向空对象
// 通过this.$refs.引用的名称 可以获取到DOM元素的引用
// console.log(this.$refs)
// 2、操作DOM元素,把文本颜色变成红色
// this.$refs.myh11.style.color = 'red'
// 4、拿到组件的引用,调用组件上的reset()方法
// console.log(this.$refs.counterRef)
this.$refs.counterRef.reset()
},
},
// 注册组件
components: {
MyCounter,
},
}
</script>
<template>
<div class="counter-container">
<h3>Counter 组件 --- {{ count }}</h3>
<hr />
<button type="button" class="btn btn-info" @click="count += 1">+1</button>
</div>
</template>
<script>
export default {
name: 'MyCounter',
data() {
return {
count: 0,
}
},
methods: {
reset() {
this.count = 0
},
},
}
</script>
<style lang="less" scoped>
.counter-container {
margin: 20px;
padding: 20px;
border: 1px solid #efefef;
border-radius: 4px;
box-shadow: 0px 1px 10px #efefef;
}
</style>
1.3 控制文本框和按钮的按需切换
-
通过布尔值 inputVisible 来控制组件中文本框与按钮的按需切换。
-
当文本框展示出来之后,如果希望它立即获得焦点,可以为其添加 ref 引用,并调用原生 DOM 对象的 .focus() 方法即可。
-
在vue中DOM元素的更新是异步进行更新的,同步执行
this.$refs.ipt.focus()
时DOM还没有完成更新,此时还没有文本框,所以拿不到最新的DOM,会报错。需要将this.$refs.ipt.focus()
这行代码进行延迟,延迟到DOM更新完成再执行,此时使用this.$nextTick(cb)
。 -
组件的 $nextTick(cb) 方法: 把cb回调推迟到下一个DOM更新周期之后执行。(即,等组件的DOM异步地重新渲染完成后,再执行cb回调函数,从而保证cb回调函数可以操作到最新的DOM元素。)
<template>
<div>
App 根组件
<hr />
<input type="text" class="form-control" v-if="inputVisible" ref="ipt" />
<button type="button" class="btn btn-primary" v-else @click="showInput">展示 input 输入框</button>
</div>
</template>
<script>
export default {
name: 'MyApp',
data() {
return {
inputVisible: false,
}
},
methods: {
showInput() {
// 要展示文本框
this.inputVisible = true
// 延迟到DOM重新渲染完成后,再执行回调函数
this.$nextTick(() => {
//console.log(this.$refs.ipt)
// 获取到文本框的DOM引用对象,然后调用 focus() 方法使其自动获得焦点
this.$refs.ipt.focus()
})
},
},
}
</script>
<style lang="less" scoped>
input.form-control {
width: 280px;
display: inline;
vertical-align: bottom;
}
</style>
2.动态组件
动态组件是指动态切换组件的显示与隐藏。
2.1 vue提供了一个内置的<component>组件,专门用来实现组件的动态渲染。
- <component> 是组件的占位符。
- 通过 is 属性动态指定要渲染的组件名称。
- <component is="要渲染的组件名称"></component>
2.2 如何实现动态组件渲染:
- 使用keep-alive保持组件状态:默认情况下,切换动态组件时无法保持组件的状态,此时可以使用vue内置的<keep-alive>组件保持动态组件的状态,从而使其不会被销毁。
<template>
<div>
App 根组件
<button type="button" class="btn btn-primary" @click="comName = 'MyHome'">首页</button>
<button type="button" class="btn btn-info ml-2" @click="comName = 'MyMovie'">电影</button>
<hr />
<!-- 保持动态组件状态 -->
<keep-alive>
<!-- 动态组件 -->
<component :is="comName"></component>
</keep-alive>
</div>
</template>
<script>
// 导入组件
import MyHome from './Home.vue'
import MyMovie from './Movie.vue'
export default {
name: 'MyApp',
data() {
return {
comName: 'MyHome'
}
},
components: {
MyHome,
MyMovie,
},
}
</script>
3.插槽
插槽是组件封装期间,为用户预留的内容占位符。
3.1 插槽的定义
- 插槽(Slot)是vue为组件封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。
3.2 插槽的基础用法
- 在封装组件时,可以通过<slot>元素定义插槽,从而为用户预留内容占位符。
- 如果在封装组件时没有预留任何<slot>插槽,则用户提供的任何自定义内容都被会丢弃。
- 封装组件时,可以为预留的<slot>插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何内容,则后备内容会生效。
<template>
<div class="com-container">
<h3>MyCom 组件 --- 插槽的基础用法</h3>
<hr />
<p>这是第一个 p 标签</p>
<slot>这里是后备内容</slot>
<p>这是最后一个 p 标签</p>
</div>
</template>
<script>
export default {
name: 'MyCom',
}
</script>
<template>
<div>
App 根组件
<hr />
<my-com>
这里是用户自定义插槽内容
</my-com>
</div>
</template>
<script>
import MyCom from './MyCom.vue'
export default {
name: 'MyApp',
components: {
MyCom,
},
}
</script>
3.3 具名插槽
- 如果在封装组件时需要预留多个插槽节点,则需要为每个<slot>插槽指定具体的name名称,这种带有具体名称的插槽叫做“具名插槽”。
- 没有指定name名称的插槽,会有隐含的名称叫做default。
- 向具名插槽提供内容时,我们可以在一个<template>元素上使用v-slot指定,并以v-slot的参数的形式提供其名称。v-slot: 可以简写为 # 。【注意:外层的<template>是不能省略的!!!】
<template>
<div>
<!-- 我们希望把页头放到这里 -->
<header>
<slot name="header"></slot>
</header>
<!-- 我们希望把主要内容放到这里 -->
<main>
<slot></slot>
</main>
<!-- 我们希望把页脚放到这里 -->
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<script>
export default {
name: 'MyArticle',
}
</script>
<template>
<div>
App 根组件
<hr />
<my-article>
<template v-slot:header>
滕王阁序
</template>
<template #default>
<p>豫章故郡,洪都新府。</p>
<p>星分翼轸,地接衡庐</p>
<p>襟三江而带五湖,控蛮荆而引瓯越。</p>
</template>
<template #footer>
<p>落款:王勃</p>
</template>
</my-article>
</div>
</template>
<script>
import MyArticle from './MyArticle.vue'
export default {
name: 'MyApp',
components: {
MyArticle,
},
}
</script>
3.4 作用域插槽
- 在封装组件的过程中,可以为预留的<slot>插槽绑定props数据,这种带有props数据的<slot>叫做“作用域插槽”。
- 使用场景:在封装组件的时候,如果不确定组件的DOM渲染成什么样子,同时用户需要获取数据,这时可以使用作用域插槽的形式把数据传递给使用者。
<template>
<div>
<h3>这是 TEST 组件</h3>
<!-- 默认插槽,默认名称为default -->
<!-- 为插槽动态地绑定数据 -->
<slot :info="infomation" :msg="message"></slot>
</div>
</template>
<script>
export default {
name: 'MyTest',
data() {
return {
infomation: {
phone: '138xxxx6666',
address: '中国北京',
},
message: 'abc'
}
},
}
</script>
<template>
<table class="table table-bordered table-striped table-dark table-hover">
<!-- 表头区域 -->
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>State</th>
</tr>
</thead>
<!-- 表格主体区域 -->
<tbody>
<!-- 循环渲染表格数据 -->
<tr v-for="item in list" :key="item.id">
<slot :user="item"></slot>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: 'MyTable',
data() {
return {
// 列表的数据
list: [
{ id: 1, name: '张三', state: true },
{ id: 2, name: '李四', state: false },
{ id: 3, name: '赵六', state: false },
],
}
},
}
</script>
<template>
<div>
App 根组件
<hr />
<!-- 使用自定义组件 MyTest -->
<my-test>
<!-- 接收插槽传过来的数据,用{ }解构赋值 -->
<template #default="{ msg, info }">
<p>{{ msg }}</p>
<p>{{ info.address }}</p>
</template>
</my-test>
<hr />
<!-- 使用自定义组件 MyTable -->
<my-table>
<template #default="{ user }">
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>
<input type="checkbox" :checked="user.state" />
</td>
</template>
</my-table>
</div>
</template>
<script>
import MyTest from './MyTest.vue'
import MyTable from './MyTable.vue'
export default {
name: 'MyApp',
components: {
MyTest,
MyTable,
},
}
</script>
4 自定义指令
vue官方提供了v-for、v-model、v-if等常用的内置指令。除此之外还允许开发者自定义指令。
4.1 私有自定义指令
- 自定义指令在使用的时候必须以v-前缀开头,但是在声明自定义指令时不需要v-前缀。
- 在每个vue组件中,可以在directives节点下声明私有自定义指令。
4.2 全局自定义指令
- 全局共享的自定义指令需要通过 “单页面应用程序的实例对象” 进行声明。
4.3 mounted 函数和 updated 函数
- mounted 函数只在元素第一次插入DOM时被调用,当DOM更新时 mounted 函数不会被触发。
- updated 函数会在每次DOM更新完成后被调用。
- 注意:在vue2.0项目中使用自定义指令时,mounted要改为bind,updated要改为update。
- 注意:如果mounted和updated函数中的逻辑完全相同,可以进行函数式的简写。
4.4 指令的参数值
- 在绑定指令时,可以通过“等号”的形式为指令绑定具体的参数值。
<template>
<div class="home-container">
<h3 v-color="'red'">MyHome 组件 --- {{ count }}</h3>
<hr />
<!-- 自定义指令 v-focus -->
<!-- 在使用自定义指令 v-color 时,可以通过=绑定指令的值 -->
<input type="text" class="form-control" v-focus v-color="'cyan'" />
<button type="button" class="btn btn-primary" @click="count += 1">+1</button>
</div>
</template>
<script>
export default {
name: 'MyHome',
data() {
return {
count: 0,
}
},
directives: {
// 自定义一个私有指令 v-focus
// focus: {
// mounted(el) { // 当被绑定的元素被渲染到DOM中时,自动触发mounted函数
// el.focus() // 让被绑定的元素自动获得焦点
// },
// },
},
}
</script>
// main.js
const app = createApp(App)
// 声明全局自定义指令
/* app.directive('focus', {
mounted(el) {
console.log('mounted')
el.focus()
},
updated(el) {
console.log('updated')
el.focus()
},
}) */
// 如果mounted和updated函数中的逻辑完全相同,可以简写为如下格式
app.directive('focus', (el) => {
el.focus()
})
app.directive('color', (el, binding) => {
// binding.value 就是通过=为指令绑定的值
el.style.color = binding.value
})
app.mount('#app')
5 Table案例
- main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import './assets/css/bootstrap.css'
import axios from 'axios'
const app=createApp(App)
axios.defaults.baseURL='https://www.escook.cn'
app.config.globalProperties.$http=axios
app.mount('#app')
- index.css
:root{
font-size: 12px;
}
body{
padding: 8px;
}
- App.vue
<template>
<div>
App根组件
<hr/>
<!-- 通过属性绑定的形式为表格指定data数据源 -->
<my-table :data="goodslist">
<!-- 表格的标题 -->
<template #header>
<th>#</th>
<th>商品名称</th>
<th>价格</th>
<th>标签</th>
<th>操作</th>
</template>
<!-- 表格每行的单元格 -->
<template #body="{row,index}">
<td>{{index+1}}</td>
<td>{{row.goods_name}}</td>
<td>¥{{row.goods_price}}</td>
<td>
<!-- 添加标签文本框 -->
<input
type="text"
class="form-control form-control-sm form-ipt"
v-if="row.inputVisible"
v-focus
v-model.trim="row.inputValue"
@blur="onInputConfirm(row)"
@keyup.enter="onInputConfirm(row)"
@keyup.esc="row.inputValue=''"
/>
<!-- 添加标签按钮 -->
<button type="button" class="btn btn-primary btn-sm" v-else @click="row.inputVisible=true">+Tag</button>
<!-- 循环渲染标签信息 -->
<span class="badge badge-warning ml-2" v-for="item in row.tags" :key="item">{{ item }}</span>
</td>
<td>
<button type="button" class="btn btn-danger btn-sm" @click="onRemove(row.id)">删除</button>
</td>
</template>
</my-table>
</div>
</template>
<script>
import MyTable from './components/my-table/MyTable.vue'
export default {
name: 'MyApp',
data(){
return{
//商品列表数据
goodslist:[]
}
},
created(){
this.getGoodsList()
},
methods:{
//初始化商品列表的数据
async getGoodsList(){
const {data:res}=await this.$http.get('/api/goods')
if(res.status!==0) return console.log('获取商品列表失败!')
this.goodslist=res.data
},
//根据id删除商品信息
onRemove(id){
this.goodslist=this.goodslist.filter(x=>x.id!==id)
},
onInputConfirm(row){
// 1、把用户在文本框中输入的值,预先转存到常量val中
const val=row.inputValue
// 2、清空文本框的值
row.inputValue=''
// 3、隐藏文本框
row.inputVisible=false
// 4、判断val的值是否为空,若为空不添加;判断val值是否存在于tags数组中,防止重复添加
if(!val||row.tags.indexOf(val)!==-1)return
// 5、将用户输入的内容,作为新标签push到当前行的tags数组中
row.tags.push(val)
},
},
directives:{
focus(el){
el.focus()
}
},
components: {
MyTable
}
}
</script>
<style lang="less" scoped>
.form-ipt {
width: 80px;
display: inline;
}
</style>
- /components/my-table/MyTable.vue
<template>
<table class="table table-bordered table-striped">
<!-- 表格:标题区域 -->
<thead>
<tr>
<!-- 为了提高组件复用性,把表格标题区域预留为具名插槽,方便使用者自定义表格标题 -->
<slot name="header"></slot>
</tr>
</thead>
<!-- 表格:内容主体区域 -->
<tbody>
<!-- 使用v-for命令,循环渲染表格的数据行 -->
<tr v-for="(item,index) in data" :key="item.id">
<!-- 为了提高组件复用性,把表格数据行区域预留为作用域插槽,方便使用者自定义表格内容 -->
<slot name="body" :row="item" :index="index"></slot>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: 'MyTable',
props:{
//表格的数据源
data:{
type:Array,
required:true,
default:[],
},
},
}
</script>
<style lang="less" scoped>
</style>
参考: https://blog.csdn.net/SongD1114/article/details/124041382