ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。
每个 vue 的组件实例上,都包含一个 $refs
对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。
<template>
<h1>App 根组件h1>
<button type="button" @click="getRefs">获取 $refs 引用button>
template>
<script>
export default {
methods: {
getRefs() {
// this 表示当前组件的实例对象 Proxy
console.log(this)
// this.$refs 默认指向空对象 {}
console.log(this.$refs)
},
},
}
script>
如果想要使用 ref 引用页面上的 DOM 元素或组件实例,则可以按照如下的方式进行操作:
<template>
<h1 ref="myr1">App 根组件h1>
<button type="button" @click="getRefs">获取 $refs 引用button>
<my-counter ref="counterRef">my-counter>
template>
<script>
import MyCounter from './Counter.vue'
export default {
methods: {
getRefs() {
// 通过 this.$refs.引用名称,获取到 DOM 元素的引用
console.log(this.$refs.myr1)
// 操作 DOM 元素,把文本颜色改成红色
this.$refs.myr1.style.color = 'red'
// 通过 this.$refs.引用名称,可以获取到组件的实例
console.log(this.$refs.counterRef)
// 获取到组件实例后,就可以调用组件上的 methods 方法
this.$refs.counterRef.reset()
},
},
components: { MyCounter },
}
script>
Counter 组件:
<template>
<h3>Counter 组件 --- {{ count }}h3>
<button type="button" @click="count += 1">+1button>
template>
<script>
export default {
name: 'MyCounter',
data() { return { count: 0 } },
methods: {
reset() { this.count = 0 },
},
}
script>
需求:控制文本框和按钮的按需切换,然后让文本框自动获得焦点
通过布尔值来控制文本框与按钮的切换。当文本框展示出来之后,添加 ref 引用,并调用原生 DOM 的 .focus() 方法让它立即获得焦点,示例代码如下:
<template>
<input type="text" v-if="inputVisible" ref="ipt" />
<button type="button" v-else @click="showInput">展示 input 输入框button>
template>
<script>
export default {
data() {
return { inputVisible: false }
},
methods: {
showInput() {
this.inputVisible = true
// 打印这个 DOM 引用,会发现此时根本没有这个 DOM 元素,如果调用 focus() 则报错
console.log(this.$refs.ipt); // undefined
// 把对 input 的操作,推迟到下次 DOM 更新之后,这样页面上就有这个 DOM 元素了
this.$nextTick(() => {
console.log(this.$refs.ipt)
// 获取到文本框的引用对象,然后调用 focus() 方法
this.$refs.ipt.focus()
})
},
},
}
script>
组件的 $nextTick(cb)
方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。通俗的理解是:等组件的 DOM 异步地重新渲染完成后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素。
动态组件指的是动态切换组件的显示与隐藏。vue 提供了一个内置的
组件,专门用来实现组件的动态渲染。
是组件的占位符,通过 is 属性动态指定要渲染的组件名称。
使用示例:
默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的
组件保持动态组件的状态。当组件在
内被切换时,它的 mounted
和 unmounted
生命周期钩子不会被调用,取而代之的是 activated
和 deactivated
。示例代码如下:
更多参数查看官网文档:https://v3.cn.vuejs.org/api/built-in-components.html#keep-alive
<template>
<button type="button" @click="comName = 'MyHome'">首页button>
<button type="button" @click="comName = 'MyMovie'">电影button>
<keep-alive>
<component :is="comName">component>
keep-alive>
template>
<script>
import MyHome from './Home.vue'
import MyMovie from './Movie.vue'
export default {
data() {
return { comName: 'MyHome' } // 1.表示当前需要渲染的组件名称
},
components: { MyHome, MyMovie },
}
script>
Home.vue
<template>
<h3>Home 组件 --- {{ count }}h3>
<button type="button" class="btn btn-primary" @click="count += 1">+1button>
template>
<script>
export default {
name: 'MyHome',
data() { return { count: 0 } },
// 如果不使用 keep-alive 会发现,每次组件切换都会进行创建或销毁
created() { console.log('created') },
unmounted() { console.log('unmounted') }
}
script>
Movie.vue
<template>
<h3>Movie 组件h3>
template>
插槽(Slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。可以把插槽认为是组件封装期间,为用户预留的内容的占位符。
在封装组件时,可以通过
元素定义插槽,从而为用户预留内容占位符。示例代码如下:
<template>
<my-com>
<img src="../../assets/logo.png" alt />
my-com>
template>
组件:
<template>
<p>这是第一个 p 标签p>
<slot>slot>
<slot>这是后备内容slot>
<p>这是最后一个 p 标签p>
template>
注意:
如果在封装组件时需要预留多个插槽节点,则需要为每个
插槽指定具体的 name 名称。这种带有具体名称的插槽叫做具名插槽。示例代码如下:
<template>
<my-article>
<template v-slot:header>
<h1>滕王阁序h1>
template>
<template v-slot:default>
<p>豫章故郡,洪都新府。p>
<p>星分翼轸,地接衡庐p>
<p>襟三江而带五湖,控蛮荆而引瓯越。p>
template>
<template #footer>
<p>落款:王勃p>
template>
my-article>
template>
组件:
<template>
<header>
<slot name="header">slot>
header>
<main>
<slot>slot>
main>
<footer>
<slot name="footer">slot>
footer>
template>
注意:
元素上使用 v-slot
指令,指定要放到具体插槽位置的名称。在封装组件的过程中,可以为预留的
插槽绑定 props 数据,这种带有 props 数据的
叫做作用域插槽。示例代码如下:
<template>
<my-test>
<template #default="scope">
<p>{{ scope }}p>
<p>{{ scope.info.address }}p>
template>
my-test>
template>
组件:
<template>
<h3>这是 Test 组件h3>
<slot :info="infomation" :msg="message">slot>
template>
<script>
export default {
data() {
return {
infomation: { phone: '138xxxx6666', address: '中国北京' },
message: 'abc'
}
},
}
script>
作用域插槽对外提供的数据对象,可以用解构赋值简化数据的接收过程。自定义单元格的渲染代码如下:
<my-table>
<template #default="{ user }">
<td>{{ user.id }}td>
<td>{{ user.name }}td>
<td>
<input type="checkbox" :checked="user.state" />
td>
template>
my-table>
组件:封装组件过程中,可以通过作用域插槽把表格每一行的数据传递给组件的使用者。
<template>
<table>
<tbody>
<tr v-for="item in list" :key="item.id">
<slot :user="item">slot>
tr>
tbody>
table>
template>
<script>
export default {
data() {
return {
list: [
{ id: 1, name: '张三', state: true },
{ id: 2, name: '李四', state: false },
{ id: 3, name: '赵六', state: false },
],
}
},
}
script>
vue 官方提供了 v-for、v-model、v-if 等常用的内置指令。除此之外 vue 还允许开发者自定义指令。
vue 中的自定义指令分为两类,分别是:私有自定义指令、全局自定义指令
在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。在使用自定义指令时,需要加上 v- 前缀。示例代码如下:
<template>
<input type="text" v-focus />
template>
<script>
export default {
directives: {
// 自定义私有指令 focus
focus: {
// 当被绑定的元素插入到 DOM 中时,会自动触发 mounted 函数
mounted(el) {
console.log(el)
el.focus() // 让被绑定饿元素自动获取焦点
},
},
},
}
script>
全局共享的自定义指令需要通过单页面应用程序的实例对象进行声明,示例代码如下:
const app = createApp(App)
// 注册一个全局自定义指令 focus
app.directive('focus', {
mounted(el) {
el.focus()
},
})
mounted 函数只在元素第一次插入 DOM 时被调用,当 DOM 更新时 mounted 函数不会被触发。 updated 函数会在每次 DOM 更新完成后被调用。示例代码如下:
app.directive('focus', {
mounted(el) { // 第一次插入 DOM 时触发这个函数
console.log('mounted')
el.focus()
},
updated(el) { // 每次 DOM 更新时都会触发 updated 函数
console.log('updated')
el.focus()
},
})
如果 mounted 和updated 函数中的逻辑完全相同,则可以简写成如下格式:
app.directive('focus', (el) => {
// 在 mounted 和 updated 时都会触发相同的业务处理
el.focus()
})
注意:在 vue2 的项目中使用自定义指令时,【 mounted -> bind 】【 updated -> update 】
在绑定指令时,可以通过等号的形式为指令绑定具体的参数值,示例代码如下:
<template>
<h3 v-color="'red'">MyHome 组件 --- {{ count }}h3>
<input type="text" v-focus v-color="'cyan'" />
<button type="button" @click="count += 1">+1button>
template>
<script>
export default {
data() {
return { count: 0 }
},
directives: {
color: (el, binding) => {
// binding.value 就是使用指令时,绑定的值
el.style.color = binding.value
},
},
}
script>
1、在终端运行如下的命令,初始化 vite 项目、安装 less 和 axios 包:
npm init vite table -- --template vue
cd table
npm install
npm i less -D
npm i axios -S
1、重置 App.vue 根组件的代码结构:
<template>
<div>
<h1>App 根组件h1>
div>
template>
<script>
export default {
name: 'MyApp',
}
script>
<style lang="less" scoped>style>
2、删除 components 目录下的 HelloWorld.vue 组件
3、重置 index.css 中的样式:
:root {
font-size: 12px;
}
body {
padding: 8px;
}
4、把 bootstrap 相关的文件放入 src/assets 目录下,然后在 main.js 中导入 bootstrap.css 和 axios 并全局配置:
import { createApp } from 'vue'
import App from './App.vue'
// 导入 bootstrap 样式表
import './assets/css/bootstrap.css'
import './index.css'
// 导入 axios
import axios from 'axios'
// 配置请求根路径
axios.defaults.baseURL = 'https://www.escook.cn'
// 将 axios 挂载为全局的 $http 自定义属性
const app = createApp(App)
app.config.globalProperties.$http = axios
app.mount('#app')
步骤分析:
- 在 App.vue 组件的 data 中声明 goodslist 商品列表数据
- 在 methods 中声明 getGoodsList 方法,用来从服务器请求商品列表的数据,然后进行赋值
- 声明 created 生命周期函数,并调用 getGoodsList 方法
- 最后使用
npm run dev
打开 vue 调试面板查看是否有数据
示例代码如下:
<template>
<div>
<h1>App 根组件h1>
div>
template>
<script>
export default {
name: 'MyApp',
data() {
return {
// 商品列表数据
goodslist: []
}
},
created() {
this.getGoodsList()
},
methods: {
// 初始化商品列表的数据
async getGoodsList() {
// 发起 Ajax 请求
const { data: res } = await this.$http.get('/api/goods')
// 请求失败
if (res.status !== 0) return console.log('获取商品列表失败!')
// 请求成功
this.goodslist = res.data
}
},
}
script>
MyTable 组件的封装要求
- 用户通过名为 data 的 prop 属性,为 MyTable.vue 组件指定数据源
- 在 MyTable.vue 组件中,预留名称为 header 的具名插槽,和名为 body 的作用域插槽
1、在 components/my-table 目录下新建 MyTable.vue 组件:
<template>
<div>MyTable 组件div>
template>
<script>
export default {
name: 'MyTable',
}
script>
<style lang="less" scoped>style>
2、在 App.vue 组件中导入并注册 MyTable.vue 组件,并进行使用:
<template>
<div>
<h1>App 根组件h1>
<hr />
<my-table>my-table>
div>
template>
<script>
// 导入 MyTable 组件
import MyTable from './components/my-table/MyTable.vue'
export default {
name: 'MyApp',
// 省略中间代码......
// 注册 MyTable 组件
components: { MyTable }
}
script>
1、基于 bootstrap 提供的 Tables,在 MyTable.vue 组件中渲染最基本的模板结构,同时,为了提高组件的复用性,最好把表格的标题区域预留为具名插槽,方便使用者自定义表格的标题:
<template>
<table class="table table-bordered table-striped">
<thead>
<tr>
<slot name="header">slot>
tr>
thead>
<tbody>tbody>
table>
template>
2、在 App.vue 组件中,通过具名插槽的形式,为 MyTable.vue 组件指定标题名称:
<my-table>
<template v-slot:header>
<th>#th>
<th>商品名称th>
<th>价格th>
<th>标签th>
<th>操作th>
template>
my-table>
1、在 MyTable.vue 组件的 props 节点中声明表格的 data 数据源:
export default {
name: 'MyTable',
props: {
// 表格的数据源
data: {
type: Array, required: true, default: [],
},
},
}
2、在 MyTable.vue 组件中,通过 v-for 指令循环渲染表格的数据行,为了提高组件的复用性,最好把表格里的 td 单元格定义为插槽,同时为了使用者能够自定义内容的渲染方式,需要把 body 定义为作用域插槽。
<template>
<table class="table table-bordered table-striped">
<thead>
<tr>
<slot name="header">slot>
tr>
thead>
<tbody>
<tr v-for="(item, index) in data" :key="item.id">
<slot name="body" :row="item" :index="index">slot>
tr>
tbody>
table>
template>
3、在 App.vue 组件中,基于作用域插槽的方式渲染表格的数据:
<my-table :data="goodslist">
<template v-slot:header>
<th>#th>
<th>商品名称th>
<th>价格th>
<th>标签th>
<th>操作th>
template>
<template v-slot:body="{ row, index }">
<td>{{ index + 1 }}td>
<td>{{ row.goods_name }}td>
<td>¥{{ row.goods_price }}td>
<td>{{ row.tags }}td>
<td>
<button type="button" class="btn btn-danger btn-sm">删除button>
td>
template>
my-table>
1、为删除按钮绑定 click 事件处理函数:
<button type="button" class="btn btn-danger btn-sm" @click="onRemove(row.id)">删除button>
2、在 App.vue 组件的 methods 中声明事件处理函数如下:
// 根据 Id 删除商品信息
onRemove(id) {
this.goodslist = this.goodslist.filter(x => x.id !== id)
},
1、修改 App.vue 组件,根据 bootstrap 提供的 Badge 效果进行循环渲染商品的标签,同时添加 input 和 button 标签,并进行按需展示,代码如下:
<td>
<input type="text" class="form-control form-control-sm ipt-tag" />
<button type="button" class="btn btn-primary btn-sm">+Tagbutton>
<span class="badge badge-warning ml-2" v-for="item in row.tags" :key="item">{{ item }}span>
td>
2、使用 v-if 和 v-else 指令和 button 的点击事件,控制 input 和 button 的按需展示。
<input type="text" class="form-control form-control-sm ipt-tag" v-if="row.inputVisible" />
<button type="button" class="btn btn-primary btn-sm" v-else
@click="row.inputVisible = true">+Tagbutton>
需求分析:
- 自定义 v-focus 指令,进行自动获取焦点
- 使用 v-model 指令把 input 输入框的值双向绑定到 row.inputValue 中
- 监听文本框的 blur 事件,触发事件时,把当前行的数据作为参数传递进去
- 声明 onInputConfirm 事件处理函数,当失去焦点时,对输入框进行隐藏
1、在 App.vue 组件中,通过 directives 节点自定义 v-focus 指令如下:
directives: {
// 封装自动获得焦点的指令
focus(el) {
el.focus()
},
},
2、为输入框添加 v-focus 指令、 v-model 的双向绑定,以及失去焦点时隐藏输入框的事件处理函数:
<input
type="text"
class="form-control form-control-sm ipt-tag"
v-if="row.inputVisible"
v-focus
v-model.trim="row.inputValue"
@blur="onInputConfirm(row)"
/>
3、在 App.vue 组件的 methods 节点下声明 onInputConfirm 事件处理函数:
onInputConfirm(row) {
// 清空文本框的值
row.inputValue = ''
// 隐藏文本框
row.inputVisible = false
}
1、进一步修改 onInputConfirm 事件处理函数如下:
onInputConfirm(row) {
// 1. 把用户在文本框中输入的值,预先转存到常量 val 中
const val = row.inputValue
// 2. 清空文本框的值
row.inputValue = ''
// 3. 隐藏文本框
row.inputVisible = false
// 4. 如果 val 值为空,或已存在 tags 数组中,则不添加
if (!val || row.tags.indexOf(val) !== -1) return
// 5. 将用户输入的内容,作为新标签 push 到当前行的 tags 数组中
row.tags.push(val)
}
当用户在文本框中敲击了回车键时,希望能够把当前输入的内容添加为 tag 标签。而敲击下 esc 键时,希望能够快速清空文本框的内容。就可以为文本框绑定 keyup 事件:
<input
type="text"
class="form-control form-control-sm ipt-tag"
v-if="row.inputVisible"
v-focus
v-model.trim="row.inputValue"
@blur="onInputConfirm(row)"
@keyup.enter="onInputConfirm(row)"
@keyup.esc="row.inputValue = ''"
/>
this.$refs
访问引用实例$nextTick
的调用时机
标签、具名插槽、作用域插槽、v-slot: 简写为 #