Vue进阶之旅:核心技术与实战(自定义指令、插槽与路由入门)
Vue进阶之旅:核心技术与实战
文章目录
- Vue进阶之旅:核心技术与实战
- 一、自定义指令
- (一)注册方式
- (二)指令的值
- (三)v-loading指令封装
- 二、插槽
- (一)默认插槽
- (二)后备内容(默认值)
- (三)具名插槽
- (四)作用域插槽
- 三、路由入门
- (一)单页应用程序(SPA)
- (二)路由概念
- (三)VueRouter的基本使用
- (四)组件目录存放问题
- 四、综合案例:商品列表
- (一)MyTag 组件
- (二)TableCase 组件
- (三)MyTable 组件
在Vue开发中,自定义指令、插槽与路由是非常重要的技术点,它们能够极大地提升我们开发的效率和灵活性。今天我们就一起来深入学习这些内容。
一、自定义指令
自定义指令允许我们封装一些DOM操作,扩展额外功能。
(一)注册方式
自己调用自己:
代码解释:
- 模板部分(
<template>
):
<template>
<div>
<h1>自定义指令</h1>
<input ref="inp" type="text">
</div>
</template>
此部分定义了一个 Vue 组件的 HTML 结构,包含一个标题 <h1>
和一个文本输入框 <input>
,ref="inp"
为输入框添加了一个引用名称。
- 脚本部分(
<script>
):
export default {
mounted () {
this.$refs.inp.focus();
}
}
此部分定义了一个 Vue 组件,在组件挂载完成(mounted
生命周期)时,通过 this.$refs.inp
找到 ref
为 inp
的输入框元素,并调用 focus()
方法让其获得焦点。
功能总结:
该 Vue 组件的主要功能是在页面上展示一个包含标题和文本输入框的区域,并在组件挂载完成时使输入框自动获得焦点,方便用户直接输入,提升用户体验。
接下来就是使用指令来获得焦点:
- 全局注册
全局注册使用Vue.directive
方法,语法如下:
Vue.directive('指令名', {
inserted (el) {
// 可以对el标签扩展额外功能,例如让元素获取焦点
el.focus()
}
})
在这里,inserted
钩子函数在被绑定元素插入父节点时调用 ,el
表示被绑定的DOM元素。举个例子:
全局注册解释:
在 Vue 中,全局注册是将组件、指令或过滤器等添加到 Vue 实例的全局范围,以便在整个应用的任何组件中使用,无需在每个组件中单独导入或声明。
下述代码中的全局注册:
Vue.directive('focus', {
inserted (el) {
el.focus();
}
})
Vue.directive('focus', {...})
用于全局注册自定义指令focus
。inserted(el) { el.focus() }
是focus
指令的一个钩子函数,当元素被插入页面时触发,使元素获得焦点。
使用场景:
<input v-focus ref="inp" type="text">
- 这里使用
v-focus
指令,因为它是全局注册的,无需额外导入或声明,元素插入页面时,会自动执行inserted
函数使输入框聚焦。
总结:
全局注册可在多个组件复用,避免重复代码,提高开发效率和可维护性,上述代码的 focus
指令就体现了这点。
- 局部注册
在组件内部通过directives
选项进行局部注册,示例代码如下:
export default {
directives: {
'指令名': {
inserted (el) {
el.focus()
}
}
}
}
在标签中使用时,写法为 <input v-指令名 type="text">
。通过自定义指令,我们可以避免在 mounted
钩子函数中使用 this.$refs.inp.focus()
这种较为繁琐的获取焦点方式,代码更加简洁。
局部注册解释:
在 Vue 中,局部注册是指将自定义指令、组件或过滤器等仅在某个组件内部进行注册的操作,使其仅在该组件内可用,而不会影响到其他组件。
代码中的局部注册:
export default {
directives : {
focus : {
inserted (el) {
el.focus();
}
}
}
}
- 在这个 Vue 组件的
script
部分,通过directives
属性进行了自定义指令的注册。 focus : {...}
定义了一个名为focus
的自定义指令。inserted (el) {...}
是该指令的一个钩子函数,当使用v-focus
指令的元素被插入页面时,会执行el.focus()
操作,使元素获得焦点。
使用场景:
<input v-focus ref="inp" type="text">
- 这里使用
v-focus
指令,在上述代码中,focus 指令只在当前组件的输入框中使用,确保了其仅在该组件内发挥作用,不会影响其他组件,这就是典型的局部注册使用场景。
总结:
上述代码中的 directives
属性的使用就是局部注册的体现,仅在当前组件内注册了 focus
指令,使其仅能在本组件内使用,与全局注册相比,其作用范围受限,但对于只在特定组件中使用的功能,局部注册能保持代码的简洁和独立性。
(二)指令的值
指令可以通过 “等号” 的形式绑定参数值,如 <div v-color="color">我是内容</div>
。在指令的定义中,通过 binding.value
可以拿到指令值,指令值修改会触发 update
函数。示例代码如下:
directives: {
color: {
inserted (el, binding) {
// 设置元素文字颜色为指令值
el.style.color = binding.value
},
update (el, binding) {
// 指令值更新时,更新元素文字颜色
el.style.color = binding.value
}
}
}
这种方式可以应对更复杂的指令封装场景。
代码示例:
<template>
<div>
<!-- 使用 v-color 指令,并将其绑定到 color1 数据 -->
<h1 v-color="color1">自定义指令1</h1>
<!-- 使用 v-color 指令,并将其绑定到 color2 数据 -->
<h1 v-color="color2">自定义指令2</h1>
</div>
</template>
<script>
export default {
data () {
return {
// 定义 color1 的数据,初始值为红色
color1 : 'red',
// 定义 color2 的数据,初始值为绿色
color2 : 'green'
}
},
directives : {
color : {
inserted (el, binding) {
// inserted 钩子函数:当指令所在元素插入页面时,将元素的颜色设置为指令绑定的值
el.style.color = binding.value;
},
update (el, binding) {
// update 钩子函数:当指令绑定的值更新时,更新元素的颜色
el.style.color = binding.value;
}
}
}
}
</script>
解释:
- 在
template
部分,我们使用了v-color
自定义指令,并将其绑定到组件中的color1
和color2
数据。 - 在
script
部分:- 首先,通过
data
函数定义了color1
和color2
的数据,分别初始化为red
和green
。 - 然后,在
directives
对象中定义了color
自定义指令。 - 对于
color
自定义指令:inserted
钩子函数:当使用v-color
指令的元素插入到页面时,会调用该函数。通过binding.value
可以获取到指令绑定的值,将元素的颜色样式设置为该值。例如,对于<h1 v-color="color1">自定义指令1</h1>
,会将该h1
元素的颜色设置为red
。update
钩子函数:当指令绑定的值发生更新时,会调用该函数。同样通过binding.value
获取更新后的值,并更新元素的颜色。例如,如果color1
的值从red
变为blue
,元素的颜色会更新为blue
。
- 首先,通过
使用场景和优势:
- 这种自定义指令的方式适用于对元素样式或其他 DOM 属性进行动态控制的场景。
- 通过
binding.value
可以灵活地将不同的数据值绑定到指令上,实现元素样式或行为的动态调整。 - 当数据发生变化时,
update
钩子函数会自动更新元素的样式,无需手动操作 DOM,符合 Vue 的数据驱动视图的理念。
注意事项:
- 确保
binding.value
提供的值符合元素属性的要求,例如,在设置颜色时,需要提供有效的颜色值,如颜色名称、十六进制代码或 RGB 值等。 - 合理使用
inserted
和update
钩子函数,根据不同的生命周期阶段实现相应的逻辑,避免冗余代码和性能问题。
通过这种方式,可以实现对元素样式的动态管理,在开发中可以根据不同的业务需求,扩展更多功能,如动态改变元素的大小、位置等,为开发更具交互性和动态性的 Vue 组件提供了很大的便利。
(三)v-loading指令封装
在实际开发中,数据请求时页面可能会空白,影响用户体验。我们可以封装 v-loading
指令来实现加载中的效果。
- 核心思路
- 准备一个
loading
类,通过伪元素定位,设置宽高实现蒙层效果,让用户知晓数据正在加载。。 - 利用
v-loading
指令,将其绑定到需要显示加载状态的元素上,并关联一个布尔变量。在数据请求开始时,将该变量置为true
,添加loading
类,显示蒙层;数据请求结束后,将该变量置为false
,移除loading
类,隐藏蒙层。
- 准备一个
- 代码实现
CSS 部分
.loading:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff url('./loading.gif') no-repeat center;
}
.box {
width: 800px;
min-height: 500px;
border: 3px solid orange;
border-radius: 5px;
position: relative;
}
.news {
display: flex;
height: 120px;
width: 600px;
margin: 0 auto;
padding: 20px 0;
cursor: pointer;
}
.news.left {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-right: 10px;
}
.news.left.title {
font-size: 20px;
}
.news.left.info {
color: #999999;
}
.news.left.info span {
margin-right: 20px;
}
.news.right {
width: 160px;
height: 120px;
}
.news.right img {
width: 100%;
height: 100%;
object-fit: cover;
}
.loading:before
样式:content: '';
:使用伪元素添加内容。position: absolute;
:设置绝对定位,使其覆盖父元素。left: 0; top: 0; width: 100%; height: 100%;
:确保覆盖整个父元素。background: #fff url('./loading.gif') no-repeat center;
:使用白色背景并在中心位置显示loading.gif
图片,实现加载效果。
JavaScript 部分
<template>
<div class="main">
<div class="box" v-loading="isLoading">
<ul>
<li v-for="item in list" :key="item.id" class="news">
<div class="left">
<div class="title">{{ item.title }}</div>
<div class="info">
<span>{{ item.source }}</span>
<span>{{ item.time }}</span>
</div>
</div>
<div class="right">
<img :src="item.img" alt="">
</div>
</li>
</ul>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
list: [],
isLoading: true
};
},
async created() {
// 发送请求获取数据
const res = await axios.get('http://hmajax.itheima.net/api/news');
setTimeout(() => {
// 模拟请求延迟,将数据更新到 list 中并将 isLoading 设为 false
this.list = res.data.data;
this.isLoading = false;
}, 2000);
},
directives: {
loading: {
inserted(el, binding) {
// 当指令插入元素时,根据 binding.value 添加或移除 loading 类
binding.value? el.classList.add('loading') : el.classList.remove('loading');
},
update(el, binding) {
// 当指令值更新时,根据 binding.value 添加或移除 loading 类
binding.value? el.classList.add('loading') : el.classList.remove('loading');
}
}
}
};
</script>
- 在 Vue 组件的
template
部分:<div class="box" v-loading="isLoading">
:使用v-loading
指令将isLoading
变量绑定到div.box
元素,控制加载状态。v-for="item in list"
:遍历list
数组,显示新闻列表。
- 在 Vue 组件的
script
部分:data()
:定义list
数组存储新闻列表,isLoading
布尔变量控制加载状态,初始为true
。created()
生命周期方法:const res = await axios.get('http://hmajax.itheima.net/api/news');
:使用axios
发送请求获取新闻数据。setTimeout(() => {...}, 2000);
:模拟 2 秒的请求延迟,完成后更新list
并将isLoading
设为false
。
directives.loading
:inserted
钩子函数:当指令插入元素时,根据binding.value
决定是否添加loading
类。update
钩子函数:当binding.value
更新时,相应更新loading
类。
使用说明
- 首先,将上述代码添加到你的 Vue 组件中,确保样式和脚本都正确引入。
- 在
data
函数中初始化isLoading
为true
,表示初始加载状态。 - 在需要显示加载效果的元素上使用
v-loading
指令,如<div v-loading="isLoading">...</div>
。 - 当发送数据请求时,保持
isLoading
为true
,请求完成后将其置为false
。
二、插槽
插槽用于让组件内部的一些结构支持自定义。
(一)默认插槽
- 作用与使用步骤
当我们希望组件的部分内容可以在使用时自定义时,可使用默认插槽。在组件内需要定制的结构部分,改用<slot></slot>
占位,使用组件时,在组件标签内部传入结构替换slot
。例如,封装一个对话框组件:
<template>
<div class="dialog">
<div class="dialog-header">
<h3>友情提示</h3>
<span class="close">✖️</span>
</div>
<div class="dialog-content">
<slot></slot>
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
</template>
使用组件时:
<MyDialog>你确认要退出本系统么?</MyDialog>
这样,对话框的内容部分就可以根据需求自定义了。
(二)后备内容(默认值)
如果希望插槽在没有传入内容时显示默认内容,可以在 <slot>
标签内放置内容作为后备内容。例如:
<template>
<div class="dialog">
<div class="dialog-header">
<h3>友情提示</h3>
<span class="close">✖️</span>
</div>
<div class="dialog-content">
<slot>我是后备内容</slot>
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
</template>
当使用组件未传入内容时,会显示 “我是后备内容”;传入内容时,则显示传入的内容。
(三)具名插槽
当组件内有多处结构需要外部传入标签进行定制时,使用具名插槽。
- 语法
在组件内,多个slot
使用name
属性区分名字,例如:
<div class="dialog-header">
<slot name="head"></slot>
</div>
<div class="dialog-content">
<slot name="content"></slot>
</div>
<div class="dialog-footer">
<slot name="footer"></slot>
</div>
在上述代码中,我们定义了一个 MyDialog
组件,它的模板包含了多个 slot
元素,每个 slot
都有一个 name
属性:
-
<slot name="head"></slot>
:定义了一个名为head
的插槽,用于显示对话框的头部内容。<slot name="content"></slot>
:定义了一个名为content
的插槽,用于显示对话框的主体内容。<slot name="footer"></slot>
:定义了一个名为content
的插槽,用于显示对话框的底部按钮或操作区域。
在使用组件时,通过 template
配合 v-slot:名字
或简化语法 #名字
来分发对应标签:
<MyDialog>
<template v-slot:head>大标题</template>
<!-- 简化语法 -->
<template #content>内容文本</template>
<template #footer>
<button>按钮</button>
</template>
</MyDialog>
通过具名插槽,可以精确地对组件内不同位置的结构进行定制。
如:
<template>
<div>
<MyDialog>
<template v-slot:head>
<div>大标题</div>
</template>
<template v-slot:content>
<div>这是内容</div>
</template>
<template #footer>
<button>取消</button>
<button>确认</button>
</template>
</MyDialog>
</div>
</template>
<script>
import MyDialog from './components/MyDialog.vue'
export default {
data () {
return {
}
},
components: {
MyDialog
}
}
</script>
-
这里我们导入了
MyDialog
组件并在父组件中使用它。 -
使用具名插槽时,可以通过以下两种语法:
-
v-slot:head
:这是完整的写法,用于向head
插槽中插入内容。 -
#footer
:这是简化写法,用于向footer
插槽中插入内容。
-
使用具名插槽的步骤和优势
步骤:
- 在组件内部,使用
<slot name="插槽名称"></slot>
标记不同的插槽位置。 - 在使用该组件的父组件中,使用
<template v-slot:插槽名称>
或<template #插槽名称>
语法将内容插入到相应的插槽中。
优势:
- 精确控制:可以精确地将内容分发到组件内的不同位置,使组件更加灵活和可定制。例如,在
MyDialog
组件中,我们可以分别将大标题、内容和按钮分发到不同的位置,而不会混淆。 - 提高组件复用性:可以使用同一个组件,根据不同的使用场景插入不同的内容,无需创建多个相似的组件。例如,我们可以使用
MyDialog
组件创建不同的对话框,只需要修改具名插槽内的内容,而不需要修改组件的结构。
注意事项:
- 确保在使用具名插槽时,插槽名称在组件内是唯一的,避免混淆。
- 可以使用
v-slot
或#
简化语法,但要注意在 Vue 2.6 及以后的版本中,v-slot
应在<template>
元素上使用,而#
是v-slot
的简化,仅在<template>
元素上使用。 - 当使用具名插槽时,插入的内容可以是 HTML 元素、文本或其他 Vue 组件。
<MyDialog>
<template v-slot:head>
<h1>欢迎使用</h1>
</template>
<template #content>
<p>请输入你的信息:</p>
<input type="text">
</template>
<template #footer>
<button @click="submit">提交</button>
</template>
</MyDialog>
(四)作用域插槽
作用域插槽可以在定义 slot
插槽的同时传值,供使用组件时使用。例如封装表格组件时,可用于定制操作列。
- 使用步骤
在组件内,给slot
标签以添加属性的方式传值:
<slot :id="item.id" msg="测试文本"></slot>
所有添加的属性会被收集到一个对象中。在使用组件时,通过 template
配合 #插槽名="obj"
接收,例如:
<MyTable :list="list">
<template #default="obj">
<button @click="del(obj.id)">删除</button>
</template>
</MyTable>
这里 default
为默认插槽名,通过这种方式可以在组件外部访问到组件内部的数据。
三、路由入门
(一)单页应用程序(SPA)
单页应用程序(SPA)是指所有功能在一个 html
页面上实现。其优点包括按需更新性能高、开发效率高、用户体验好;缺点是学习成本高、首屏加载慢、不利于SEO。适用于系统类网站、内部网站、文档类网站、移动端站点等。
(二)路由概念
在Vue中,路由是路径和组件的映射关系。例如,http://localhost:8080/#/home
对应首页组件,http://localhost:8080/#/comment
对应评论组件等。通过路由,我们可以根据不同的路径匹配渲染相应的组件。
(三)VueRouter的基本使用
VueRouter是Vue官方的路由插件,用于实现路径改变时切换显示匹配的组件。
- 使用步骤(5 + 2)
-
5个基础步骤
- 下载:使用
yarn add vue-router@3.6.5
下载VueRouter模块到当前工程。[注]在下载前,请用rm -r node_modules 删除原先的node_modules,否则会产生报错(因人而异) - 引入:在项目中引入VueRouter,
import VueRouter from 'vue-router'
。 - 安装注册:通过
Vue.use(VueRouter)
进行安装注册。 - 创建路由对象:
const router = new VueRouter()
。 - 注入:将路由对象注入到
new Vue
实例中,new Vue({ render: h => h(App), router }).$mount('#app')
。 -
import Vue from 'vue' import App from './App.vue' import VueRouter from 'vue-router' Vue.use(VueRouter) const router = new VueRouter() Vue.config.productionTip = false new Vue({ render: h => h(App), router : router }).$mount('#app')
- 下载:使用
-
2个核心步骤
- 创建组件并配置路由规则:在
views
目录下创建组件,如Find.vue
、My.vue
、Friend.vue
,然后配置路由规则:
- 创建组件并配置路由规则:在
-
import Find from './views/Find.vue'
import My from './views/My.vue'
import Friend from './views/Friend.vue'
const router = new VueRouter({
routes: [
{ path: '/find', component: Find },
{ path: '/friend', component: Friend },
{ path: '/my', component: My }
]
})
- **配置导航和路由出口**:在页面中配置导航链接,如 `<a href="#/find">发现音乐</a>`,并设置路由出口 `<router-view></router-view>`,用于显示匹配路径的组件。
(四)组件目录存放问题
.vue
文件分为页面组件和复用组件两类。页面组件放在 src/views
文件夹,用于配合路由进行页面展示;复用组件放在 src/components
文件夹,用于封装复用。例如,Home.vue
(首页)、Category.vue
(分类页)等页面组件放在 views
目录,Comment.vue
(评价组件)等复用组件放在 components
目录,这样分类存放更易于维护。
通过今天对Vue自定义指令、插槽与路由入门的学习,我们对Vue的核心技术有了更深入的理解。这些技术在实际项目开发中非常实用,能够帮助我们构建出更加灵活、高效的应用程序。在后续的学习和实践中,我们还需要不断地运用和巩固这些知识,提升自己的开发能力。 在深入学习 Vue 开发的过程中,我们已经对自定义指令、插槽和路由入门有了一定的了解。下面我们将通过一些实际案例来进一步加深对这些知识的理解和运用,同时对之前学习的内容进行更全面的总结。
四、综合案例:商品列表
在商品列表的实现中,我们封装了两个重要组件:标签组件(MyTag)和表格组件(MyTable),这两个组件综合运用了之前所学的多种技术。
以下是添加中文注释后的代码:
(一)MyTag 组件
1. 双击标签显示输入框,并且输入框自动获取焦点。
<template>
<div class="my-tag">
<!-- 使用 v-if 判断是否显示输入框,根据 isEdit 的值决定 -->
<input
v-if="isEdit"
class="input"
v-focus
:value="value"
ref="inp"
type="text"
placeholder="输入标签"
<!-- 当输入框失去焦点时触发,将 isEdit 设为 false 以隐藏输入框 -->
@blur="isEdit = false"
<!-- 当用户按下回车键时触发 handleEnter 方法 -->
@keyup.enter="handleEnter"
/>
<!-- 当不满足 v-if 条件时显示该 div 元素,用户双击时触发 handleClick 方法 -->
<div v-else @dblclick="handleClick" class="text">{{value}}</div>
</div>
</template>
<script>
export default {
props : {
// 接收父组件传递的标签信息,用于显示和修改
value : String
},
data () {
return {
// 用于控制输入框的显示和隐藏,初始为 false 表示输入框隐藏
isEdit : false
}
},
methods : {
handleClick () {
// 当用户双击标签时,将 isEdit 设为 true,使输入框显示
this.isEdit = true
},
}
}
</script>
- 模板部分:
<div class="my-tag">
:
// 定义MyTag
组件的容器元素。<input v-if="isEdit" class="input" v-focus ref="inp" type="text" placeholder="输入标签" @blur="isEdit = false" @keyup.enter="handleEnter" />
:v-if="isEdit"
:
// 根据isEdit
变量的值来决定是否显示输入框,初始状态下isEdit
为false
,输入框不显示。v-focus
:
// 是一个自定义指令,当输入框元素插入页面时,会触发inserted
钩子函数(该指令在 Vue 实例中已全局注册,未在此组件中显示,但应该存在于项目的其他地方),调用el.focus()
使输入框自动获得焦点。ref="inp"
:
// 为输入框添加一个引用,方便在组件内部通过this.$refs
访问该元素。@blur="isEdit = false"
:
// 当输入框失去焦点时,将isEdit
的值置为false
,用于后续隐藏输入框的操作。@keyup.enter="handleEnter"
:
// 为输入框添加@keyup.enter
事件监听器,用于处理回车键事件。
<div v-else @dblclick="handleClick" class="text">{{value}}</div>
:v-else
:
// 与v-if
相对应,当isEdit
为false
时显示该div
元素。@dblclick="handleClick"
:
// 为该div
元素添加双击事件,当用户双击时调用handleClick
方法。
- 脚本部分:
props : {...}
:value : String
:
// 接收父组件传递过来的标签信息,用于显示和修改。
data () {...}
:isEdit : false
:
// 一个数据属性,用于控制输入框的显示与隐藏,初始状态下输入框处于隐藏状态。
methods : {...}
:handleClick () {...}
:this.isEdit = true
:
// 双击div
元素时,将isEdit
的值置为true
,从而触发v-if
条件,使输入框显示,同时由于v-focus
指令的存在,输入框会自动获得焦点。
2. 当输入框失去焦点时,隐藏输入框。
<template>
<div class="my-tag">
<input
v-if="isEdit"
class="input"
v-focus
:value="value"
ref="inp"
type="text"
placeholder="输入标签"
@blur="isEdit = false"
@keyup.enter="handleEnter"
/>
<div v-else @dblclick="handleClick" class="text">{{value}}</div>
</div>
</template>
<script>
export default {
props : {
value : String
},
data () {
return {
isEdit : false
}
},
methods : {
handleClick () {
this.isEdit = true
},
}
}
</script>
- 模板部分:
<input... @blur="isEdit = false" />
:@blur="isEdit = false"
:
// 为输入框添加@blur
事件监听器,当输入框失去焦点时,将isEdit
的值置为false
,根据v-if
条件,输入框会隐藏。
3. 能够回显标签原本的信息。
<template>
<div class="my-tag">
<input
v-if="isEdit"
class="input"
v-focus
:value="value"
ref="inp"
type="text"
placeholder="输入标签"
@blur="isEdit = false"
@keyup.enter="handleEnter"
/>
<div v-else @dblclick="handleClick" class="text">{{value}}</div>
</div>
</template>
<script>
export default {
props : {
value : String
},
data () {
return {
isEdit : false
}
},
methods : {
handleClick () {
this.isEdit = true
},
}
}
</script>
- 模板部分:
<input v-if="isEdit"... :value="value" />
::value="value"
:
// 使用:value
绑定了value
属性,将父组件传递过来的值显示在输入框中,实现输入框显示标签原本信息的功能。
<div v-else @dblclick="handleClick" class="text">{{value}}</div>
:{{value}}
:
// 使用双大括号插值表达式将value
的值显示在div
元素中,当输入框隐藏时,回显标签原本的信息。
4. 在输入框中修改内容后,按下回车键可以修改标签信息。
<template>
<div class="my-tag">
<input
v-if="isEdit"
class="input"
v-focus
:value="value"
ref="inp"
type="text"
placeholder="输入标签"
@blur="isEdit = false"
@keyup.enter="handleEnter"
/>
<div v-else @dblclick="handleClick" class="text">{{value}}</div>
</template>
<script>
export default {
props : {
value : String
},
data () {
return {
isEdit : false
}
},
methods : {
handleClick () {
this.isEdit = true
},
handleEnter (e) {
// 当用户按下回车键时,检查输入框内容是否为空
if(e.target.value.trim() === '')
{
// 若输入框内容为空,弹出警告信息
return alert('标签内容不能为空!')
}
// 不为空时,将修改后的值通过 $emit 发送给父组件
this.$emit('input', e.target.value)
// 输入框修改完成后,将 isEdit 设为 false 以隐藏输入框
this.isEdit = false
},
}
}
</script>
- 模板部分:
<input... @keyup.enter="handleEnter" />
:@keyup.enter="handleEnter"
:
// 为输入框添加@keyup.enter
事件监听器,当用户在输入框中按下回车键时,会触发handleEnter
方法。
- 脚本部分:
methods : {...}
:handleEnter (e) {...}
:if(e.target.value.trim() === '') {...}
:
// 检查输入框中的内容是否为空,如果为空则弹出警告信息,防止输入空标签。this.$emit('input', e.target.value)
:
// 当用户输入不为空时,通过$emit
方法触发input
事件,并将修改后的值作为参数传递出去,通知父组件标签信息已修改。this.isEdit = false
:
// 在输入框修改完成后,将isEdit
的值置为false
,隐藏输入框。
(二)TableCase 组件
<template>
<div class="table-case">
<table class="my-table">
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>图片</th>
<th width="100px">标签</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>梨皮朱泥三绝清代小品壶经典款紫砂壶</td>
<td>
<img src="https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg" />
</td>
<td>
<!-- 使用 v-model 将 MyTag1 组件的 value 属性和父组件的 tempText 数据进行双向绑定 -->
<MyTag1 v-model="tempText"></MyTag1>
</td>
</tr>
<tr>
<td>1</td>
<td>梨皮朱泥三绝清代小品壶经典款紫砂壶</td>
<td>
<img src="https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg" />
</td>
<td>
<!-- 使用 v-model 将 MyTag1 组件的 value 属性和父组件的 tempText2 数据进行双向绑定 -->
<MyTag1 v-model="tempText2"></MyTag1>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import MyTag1 from './components/MyTag1.vue';
export default {
name: 'TableCase',
components: {
MyTag1
},
data() {
return {
// 存储初始的标签信息,将通过 v-model 传递给 MyTag1 组件
tempText : '茶具',
tempText2 : '鞋子',
goods: [
{
id: 101,
picture:
'https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg',
name: '梨皮朱泥三绝清代小品壶经典款紫砂壶',
tag: '茶具',
},
{
id: 102,
picture:
'https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg',
name: '全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌',
tag: '男鞋',
},
{
id: 103,
picture:
'https://yanxuan-item.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png',
name: '毛茸茸小熊出没,儿童羊羔绒背心73-90cm',
tag: '儿童服饰',
},
{
id: 104,
picture:
'https://yanxuan-item.nosdn.127.net/56eb25a38d7a630e76a608a9360eec6b.jpg',
name: '基础百搭,儿童套头针织毛衣1-9岁',
tag: '儿童服饰',
},
],
}
},
}
</script>
- 模板部分:
<td><MyTag1 v-model="tempText"></MyTag1></td>
和<td><MyTag1 v-model="tempText2"></MyTag1></td>
:v-model="tempText"
和v-model="tempText2"
:
// 使用v-model
将MyTag1
组件的value
属性和父组件的tempText
或tempText2
数据进行双向绑定。将tempText
和tempText2
的初始值传递给MyTag1
组件,实现标签信息的初始化显示,同时当MyTag1
组件内部修改标签信息时,也会更新tempText
和tempText2
的值。
- 脚本部分:
data() {...}
:tempText : '茶具'
和tempText2 : '鞋子'
:
// 存储了初始的标签信息,通过v-model
传递给MyTag1
组件,实现标签信息的显示和更新。
总结:
MyTag
组件通过 Vue 的 v-if
、v-else
、自定义指令 v-focus
、事件监听器 @dblclick
、@blur
、@keyup.enter
和 v-model
等技术,实现了以下四个功能:
- 双击标签显示输入框,并且输入框自动获取焦点:通过
v-if
和v-else
控制输入框的显示和隐藏,利用@dblclick
事件处理双击操作,结合v-focus
指令实现输入框自动聚焦。 - 当输入框失去焦点时,隐藏输入框:使用
@blur
事件监听器,当输入框失去焦点时将isEdit
置为false
,实现输入框的隐藏。 - 能够回显标签原本的信息:通过
v-model
实现输入框和父组件数据的双向绑定,使用双大括号插值表达式{{value}}
回显标签信息。 - 在输入框中修改内容后,按下回车键可以修改标签信息:利用
@keyup.enter
事件监听器,在用户按下回车键时调用handleEnter
方法,该方法中通过$emit
向父组件发送更新事件和修改后的数据,同时将isEdit
置为false
隐藏输入框。
注意事项:
- 确保
v-model
的双向绑定正确工作,数据在父组件和子组件之间能准确传递和更新。 - 对于自定义指令
v-focus
,需确保其在 Vue 实例中已正确注册,且作用符合预期。 - 在
handleEnter
方法中,可根据实际需求添加更多的输入验证逻辑,以确保输入内容的合法性和合理性。
(三)MyTable 组件
1. 能够动态传递表格数据并进行渲染,以适应不同的数据来源。
<template>
<div class="table-case">
<MyTable1 :data="goods">
</MyTable1>
</div>
</template>
<script>
import MyTable1 from './components/MyTable1.vue';
import MyTag1 from './components/MyTag1.vue';
export default {
name: 'TableCase',
components: {
MyTag1,
MyTable1
},
data() {
return {
goods: [
{
id: 101,
picture: 'https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg',
name: '梨皮朱泥三绝清代小品壶经典款紫砂壶',
tag: '茶具',
},
{
id: 102,
picture: 'https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg',
name: '全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌',
tag: '男鞋',
},
{
id: 103,
picture: 'https://yanxuan-item.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png',
name: '毛茸茸小熊出没,儿童羊羔绒背心73-90cm',
tag: '儿童服饰',
},
{
id: 104,
picture: 'https://yanxuan-item.nosdn.127.net/56eb25a38d7a630e76a608a9360eec6b.jpg',
name: '基础百搭,儿童套头针织毛衣1-9岁',
tag: '儿童服饰',
},
],
}
},
}
</script>
<template>
<table class="my-table">
<tbody>
<tr v-for="(item, index) in data" :key="item.id">
</tr>
</tbody>
</table>
</template>
<script>
export default {
props : {
data : {
type : Array,
required : true
},
}
}
</script>
解释:
- 在
TableCase
组件中:<MyTable1 :data="goods">
:- 这里通过
:data="goods"
将goods
数组数据传递给MyTable1
组件。goods
数组存储了表格所需的数据,不同的数据源可以替换这个数组,实现动态传递表格数据的功能,使MyTable1
组件可以根据不同的数据源进行表格渲染。
- 这里通过
- 在
MyTable1
组件中:props : { data : { type : Array, required : true } }
:- 接收父组件传递过来的
data
属性,要求该属性必须是数组类型,接收的数据将作为表格渲染的数据源,从而实现表格的动态渲染。
- 接收父组件传递过来的
2. 表头部分支持用户自定义,满足多样化的展示需求。
<template>
<div class="table-case">
<MyTable1 :data="goods">
<template #head>
<!-- 用户自定义的表头 -->
<th>编号</th>
<th>名称</th>
<th>图片</th>
<th width="100px">标签</th>
</template>
</MyTable1>
</div>
</template>
<template>
<table class="my-table">
<thead>
<tr>
<slot name="head"></slot>
<!-- 具名插槽 head,用于接收父组件自定义的表头内容 -->
</tr>
</thead>
</table>
</template>
解释:
- 在
TableCase
组件中:<template #head>
:- 使用
#head
具名插槽,用户可以在此自定义表头的内容,这里定义了 “编号”、“名称”、“图片”、“标签” 等表头信息,可根据不同的需求修改这些内容,实现表头的自定义,满足多样化的展示需求。
- 使用
- 在
MyTable1
组件中:<slot name="head"></slot>
:- 在
thead
中定义了名为head
的具名插槽,用于接收父组件传递过来的自定义表头内容。
- 在
3. 表格主体部分同样支持用户自定义,方便用户根据业务需求定制表格的显示内容和样式。
<template>
<div class="table-case">
<MyTable1 :data="goods">
<template #body="{item, index}">
<!-- 用户自定义的表格主体内容,根据 item 和 index 渲染每一行的数据 -->
<td>{{index + 1}}</td>
<td>{{item.name}}</td>
<td>
<img :src="item.picture" />
</td>
<td>
<MyTag1 v-model="item.tag"></MyTag1>
</td>
</template>
</MyTable1>
</div>
</template>
<template>
<table class="my-table">
<tbody>
<tr v-for="(item, index) in data" :key="item.id">
<slot :item="item" :index="index" name="body"></slot>
<!-- 具名插槽 body,用于接收父组件自定义的表格主体内容,并接收 item 和 index 数据 -->
</tr>
</tbody>
</table>
</template>
解释:
- 在
TableCase
组件中:<template #body="{item, index}">
:- 使用
#body
具名插槽并接收item
和index
参数,用户可以根据item
(每一行的数据对象)和index
(行索引)来自定义表格主体部分的内容,如这里显示了编号、名称、图片和MyTag1
组件。用户可以根据业务需求修改此处的内容,定制表格主体的显示内容和样式。
- 使用
- 在
MyTable1
组件中:<slot :item="item" :index="index" name="body"></slot>
:- 在
tbody
中定义了名为body
的具名插槽,接收父组件传递过来的自定义表格主体内容,并将item
和index
作为参数传递给父组件,方便用户使用这些数据进行定制化开发,以实现表格主体部分的自定义显示。
- 在
总结:
MyTable
组件通过 props
接收父组件传递的数据,并通过具名插槽的方式支持用户对表头和表格主体部分的自定义,以满足不同的数据来源和多样化的展示需求。在 TableCase
组件中,用户可以通过向 MyTable1
组件传递数据和使用具名插槽来自定义表格的显示内容和样式,实现了以下功能:
- 动态传递表格数据并进行渲染:使用
:data="goods"
传递数据给子组件,并通过props
接收数据,实现动态渲染表格。 - 表头部分支持用户自定义:通过
<template #head>
具名插槽,用户可以自定义表头的显示内容。 - 表格主体部分支持用户自定义:通过
<template #body="{item, index}">
具名插槽,用户可以根据item
和index
来自定义表格主体的显示内容和样式,同时还可以使用其他组件(如MyTag1
)来丰富表格的功能。