vue的组件封装
一、为什么要封装组件(组件化开发)
组件化开发(封装组件)的好处
好处显而易见,可以增加代码的复用性、灵活性,从而提高开发效率。试想如果一个项目中在很多页面都能用到一个弹出框,若在每个页面都去写一套弹出框的结构样式和对应js的逻辑。这样的话,开发效率会大打折扣; 当然现在有很多优秀的组件库.不过我们只是通过这个案例来熟悉组件的封装
组件的封装目前用到的三个技能点:
1父往子传值
2.子往父传值
3.插槽技术 (文末有介绍)
另外在本文中还有一些琐碎知识点: 属性修饰符.sync 能够让我们子组件修改父组件传递过来的属性
时间修饰符 .self .stop
vue里面的内置动画标签 <transition>
父组件修改子组件里面的数据
二、dialog组件结构搭建
dialog对话框,整体有一个动画效果,vue的动画效果,使用transition包裹需要动画展示的元素,那么这个元素在显示/隐藏时自动添加一些类名,此例详见后续代码
需求:
●弹出框弹出或关闭设置过渡动画
●使用默认插槽做主要内容区
●使用具名插槽指定对应弹框头部内容和弹框底部内容
●弹框遮罩层背景的开启或关闭
●点击遮罩层控制弹框的关闭与否
●是否显示关闭的叉号小图标
●自定义弹框的title标题
对话框分为三部分:
1、头部:左侧为标题,使用具名插槽 title 占位,右侧为按钮/图标(关闭)
2、主体内容,使用不具名的插槽占位
3、底部:一般都是一些操作,使用具名插槽 footer 占位,通常内容是取消/确认按钮
需要传入的参数:
title:头部标题
width:对话框宽度(我觉得这里也可以在父级用deep指定)
top:对话框距离顶部的距离
visible:对话框的显示/隐藏
具体实现代码:
1.在src根目录下面的components里面新建一个Dialog.vue文件
<template> <!-- 打开弹框的动画 --> <!-- vue的内置动画标签,会根据name属性,形成6个样式 动画进入的三个状态 dialog-fade-enter 准备进入 dialog-fade-enter-active 进入的过程中 dialog-fade-enter-to 动画进入结束 dialog-fade-leave 动画准备离开 dialog-fade-leave-active 动画离开过程中 dialog-fade-leave-to 动画离开完成 --> <transition name="dialog-fade"> <!-- div整个屏幕的半透明阴影 --> <div class="dialogBox" :class="{ isShowMask: mask == true }" v-show="isShowDialog" @click.self="clickMaskCloseFn" > <!-- 弹出框 --> <div class="dialogBoxContent" @click.stop> <div class="headhead"> <!-- 具名插槽带插槽的默认值: 这样写可以做到若有传递过来的title就用传递过来的title 若有传递过来的插槽,就以插槽的为准 --> <slot name="header"> <span>{{ title }}</span> </slot> <i class="el-icon-close" @click="close" v-show="showCloseIcon">X </i> </div> <div class="bodybody"> <!-- 内容区我们使用默认插槽 --> <slot></slot> </div> <div class="footfoot"> <!-- 底部使用命名插槽 --> <slot name="footer"></slot> </div> </div> </div> </transition> </template> <script> export default { name: 'dialogComponent', props: { // 控制是否展示或隐藏对话框 isShowDialog: { type: Boolean, default: false }, // 父组件传过来的标题值 title: { type: String, default: '' }, // 是否显示关闭小图标 showCloseIcon: { type: Boolean, default: true }, // 是否开启背景遮罩层 mask: { type: Boolean, default: true }, // 是否点击遮罩层mask关闭弹出框 clickMaskClose: { type: Boolean, default: false } }, data () { return {} }, methods: { // 关闭弹出框 close () { this.$emit('update:isShowDialog', false) }, // 点击遮罩层关闭弹框 clickMaskCloseFn () { if (this.clickMaskClose === true) { this.$emit('update:isShowDialog', false) } else { /* 这里要控制一下冒泡事件,注意第十行使用@click.stop 不控制冒泡的话,点击内容区也会导致弹出框关闭 */ } } } } </script> <style lang="scss" scoped> .dialogBox { width: 100%; height: 100%; position: fixed; top: 0; left: 0; display: flex; justify-content: center; align-items: center; .dialogBoxContent { width: 500px; height: 220px; border: 2px solid #e9e9e9; border-radius: 20px; background-color: #fff; .headhead { width: 100%; height: 60px; line-height: 60px; border-bottom: 1px solid #e9e9e9; box-sizing: border-box; padding: 20px; display: flex; justify-content: space-between; align-items: center; span { font-size: 24px; } i { font-size: 24px; cursor: pointer; } } .bodybody { width: 100%; height: calc(100% - 120px); } .footfoot { width: 100%; height: 60px; line-height: 60px; box-sizing: border-box; border-top: 1px solid #e9e9e9; padding: 0 20px; .el-button { margin-left: 12px; } } } } .isShowMask { background-color: rgba(0, 0, 0, 0.3); } .dialog-fade-enter, .dialog-fade-leave-to { opacity: 0; } .dialog-fade-enter-active, .dialog-fade-leave-active { transition: opacity 0.3s; } // 还可以这样定义动画哦 // 进入动画 // .dialog-fade-enter-active { // animation: dialog-fade-in 0.4s; // } // // 离开动画 // .dialog-fade-leave-active { // animation: dialog-fade-out 0.4s; // } // @keyframes dialog-fade-in { // 0% { // transform: translate3d(0, -20px, 0); // opacity: 0; // } // 100% { // transform: translate3d(0, 0, 0); // opacity: 1; // } // } // @keyframes dialog-fade-out { // 0% { // transform: translate3d(0, 0, 0); // opacity: 1; // } // 100% { // transform: translate3d(0, -20px, 0); // opacity: 0; // } // } </style>
2全局注册组件(main.js)
import NewDialog from '@/components/Dialog'
Vue.component('myDialog', NewDialog)
3.业务组件中的调用
<!-- sync:事件修饰符,是一个语法糖写法,实现子组件修改父组件传入的props 父:visible.sync="visible" 子:this.$emit("update:visible", false) --> <my-dialog :isShowDialog.sync="isShowDialog" title="设置标题" :showCloseIcon="true" :mask="true" :clickMaskClose="true" > <!-- 要与组件的具名插槽对应 --> <template #header> 具名插槽 </template> <template> 默认插槽 </template> <!-- 要与子组件的插槽对应 --> <template #footer> <van-button size="small" @click="isShowDialog = false">取消</van-button> <van-button type="primary" size="small" @click="isShowDialog = false" >确认</van-button > </template> </my-dialog> <van-button @click="isShowDialog = true" type="primary">模态框</van-button>
三、知识点回顾总结
为什么要有插槽
插槽api的诞生源自于vue数据传递的需求,因为平常我们使用props父向子传递数据,传递的数据都是对象、数组、字符串等"js类型的数据"。当我们想要传递大量的html类型的片段数据怎么办?
有这样的需求,于是插槽的技术就应运而生了。
组件化编程中,css不怎么需要传递,因为我们可以通过深度作用域选择器,如/deep/去在父组件中选中子组件中的dom元素去设置样式
插槽的分类
●默认插槽(又叫:普通插槽、单个插槽,匿名插槽。即不带名字的,不用设置name属性 <slot></slot>这样的)
●具名插槽(带个名字的 <slot name="footer"></slot>这样的,拥有name属性的)
●作用域插槽(这个是插槽的略微高级点用法,就案例而言,在饿了么UI中的表格中有用到)
插槽可以在饿了么UI或者antD中封装的组件中看到,以el-dialog为例,其使用到了默认插槽和具名插槽。可以这样说,UI组件中基本上都使用了插槽技术,大家没事可以去看看饿了么UI的封装组件的源码,还是很有收获的
路径如下:在vue项目中,打开node_modules,里面有很多的包/组件,找到element-ui下的packages,里面都是饿了么封装的组件
作用域插槽本篇文章暂时用不到,所以先按下不表,后续再单独写一篇关于作用域插槽的文章
匿名(默认)插槽的使用
子组件放置匿名插槽
<template> <div class="box"> <h1>我是子组件</h1> <!-- 第一步,在子组件里面,找个地方插入一个槽,因为槽可以盛放东西,又因为子组件会引入到父组件中 所以子组件插入的这个插槽具体装什么东西,肯定是由父组件去装东西,父组件装html片段, 在子组件中就可以得到html片段。所以:插槽实现了父组件向子组件传递数据的效果 --> <slot></slot> <!-- 子组件写了一个这样的slot标签才能接收到父组件传递过来的html片段。不写的话父组件传了也是白传 即不会生成DOM,也不会渲染 --> </div> </template> <script> export default { name: "DemoChildslot", }; </script> <style lang="less" scoped> .box { width: 200px; height: 200px; background-color: #baf; } </style>
父组件使用匿名插槽传递HTML
<template> <div id="app"> <!-- 第二步 使用子组件,在子组件标签中间写入代码,就可以实现插槽的数据传递了。 --> <child-slot> <i>我是父组件传递过去的</i> <!-- 也可以这样写,因为默认就是slot="default",不过一般不这样写,略麻烦 <i slot="default">我是父组件传递过去的</i> --> </child-slot> </div> </template> <script> // 引入子组件 import childSlot from "./childSlot"; export default { components: { childSlot, // 注册子组件 }, }; </script> <style lang="less" scoped> #app { width: 100%; min-height: 100vh; box-sizing: border-box; padding: 50px; } </style>

比如我们想要封装一个弹框组件,弹框弹出以后,在弹框的内容区,有弹框头部内容区、弹框主要内容区、弹框底部内容区。弹框主要内容区我们可以使用匿名插槽、如果弹框要指定头部的内容、指定弹框的底部内容,可以通过具名插槽指定具体内容。结构代码如下:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了