合格前端系列第二弹-Vue组件开发续篇

2023-10-09 03:30

本文主要是介绍合格前端系列第二弹-Vue组件开发续篇,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

之前我写过一篇有关vue组件开发的文章,这次将是对上次的一次拓展。其中也会有vue部分源码的解析,接下来直接上正文吧。

一、父子组件之间的通信

总所周知,如果进行组件开发的话,必定存在组件通信的问题,具体通信如何进行的呢,我借用一张vue官网的图
182832_LQH3_2912341.png

图中很明显可以看到,Parent组件通过props向下传递数据(props down),Child组件通过events向上传递消息(events up)。具体通信机制,请转链接 https://vuejs.org/v2/guide/components.html。那么如果不是父子组件关系,而是slot节点之间的关系,又该如何进行通信呢。下面的内容会带着大家一步一步解惑。

二、组件的开发

接下里的例子我将模拟element-ui中的dropdown下拉菜单组件,对组件开发进行详细的解剖。进行开发前我们先看一下element-ui中的dropdown组件实现了哪些功能(具体功能转链接http://element.eleme.io/#/zh-CN/component/dropdown),这里我们挑选一些不会涉及到调用其他element-ui组件的功能,接下来,希望小伙伴们跟着我一起慢慢实现一个属于自己的element-ui组件吧。

1、组件设计

173744_U55N_2912341.png

如上图所示,整个dropdown组件分成了三个组件模块,最外层的dropdown,下拉菜单dropdown-menu,以及下拉列表dropdown-list。至于为何这样设计,主要是为了该组件的cover范围可以大,可以适用各种场景。

我们先看下实现功能后每个组件对应的template内容
a、dropdown组件

<style media="screen">
.v-dropdown {display: inline-block;position: relative;color: #48576a;font-size: 14px;
}
</style>
<template>
<div class="v-dropdown":trigger="trigger":visible="visible":hideOnClick="hideOnClick"v-clickoutside="hide"
><slot></slot>
</div>
</template>

b、dropdown-menu组件

<style media="screen">
.v-dropdown-menu {margin: 5px 0;background-color: #fff;border: 1px solid #d1dbe5;box-shadow: 0 2px 4px rgba(0,0,0,.12), 0 0 6px rgba(0,0,0,.12);padding: 6px 0;z-index: 10;position: absolute;top: 20px;left: 0;min-width: 100px;
}
</style>
<template>
<ul class="v-dropdown-menu" v-show="visible"><slot></slot>
</ul>
</template>

c、dropdown-list组件

<style media="screen">
ul, li {list-style: none;
}
.v-dropdown-menu_list {cursor: pointer;
}
</style>
<template>
<li class="v-dropdown-menu_list"@click="handleClick":command="command"
><slot></slot>
</li>
</template>

如上,大家也可以看出来,dropdown组件负责一个全局的控制,他通过向dropdown-menu组件传递visible属性控制着其消失与显示,对于dropdown-list组件点击事件的回调与否的控制则是通过$emit监听dropdown组件中是否存在自定义事件command。

2、组件功能的实现

a、dropdown-menu消失与显示

首先我们实现一个基本功能,通过hover或者click事件控制dropdown-menu组件的显示与否,这里我们需要给dropdown组件绑定两个属性,一个是visible,一个是trigger。

<template>
<div class="v-dropdown":trigger="trigger":visible="visible"
><slot></slot>
</div>
</template>

这里我们需要先重写两个方法,一个是broadcast(向下传递),一个是dispatch(向上传递),后面的传递也基本基于这两种方法。具体的mixins方法emitter.js如下

/*** [broadcast 上下传递]* @param  {[type]} componentName [组件别名]* @param  {[type]} eventName     [事件别名]* @param  {[type]} params        [事件回调参数]*/
function broadcast(componentName, eventName, params) {// 遍历当前实例的children节点this.$children.forEach(child => {var name = child.$options.componentName;// 如果子节点名称和组件别名相同,则当前子节点为目标节点if (name === componentName) {// 找到目标节点后,触发事件别名对应的回调,并将参数传入child.$emit.apply(child, [eventName].concat(params));}// 如果子节点名称和组件别名不相同,继续遍历子节点的子节点,以此类推,直到找到目标节点else {broadcast.apply(child, [componentName, eventName].concat([params]));}});
}
/*** [dispatch 向上传递]* @param  {[type]} componentName [组件别名]* @param  {[type]} eventName     [事件别名]* @param  {[type]} params        [事件回调参数]*/
function dispatch(componentName, eventName, params) {var parent = this.$parent || this.$root;var name = parent.$options.name;// 向上找目标父节点,如果上一级父节点不符合,则继续往上查询while (parent && (!name || name !== componentName)) {parent = parent.$parent;if (parent) {name = parent.$options.name;}}// 找到目标父节点后,触发事件别名对应的回调,并将参数传入if (parent) {parent.$emit.apply(parent, [eventName].concat(params));}
}
export default {methods: {broadcast(componentName, eventName, params) {broadcast.apply(this, [componentName, eventName, params]);},dispatch(componentName, eventName, params) {dispatch.apply(this, [componentName, eventName, params]);}}
};

好了对于事件传递的mixins方法我们已经写好了,接下来我们需要做的就是通过在dropdown-menu组件中注册好visible事件,代码如下

<template>
<ul class="v-dropdown-menu" v-show="visible"><slot></slot>
</ul>
</template><script>
export default {name: 'VDropdownMenu',componentName: 'VDropdownMenu',// 组件create的时候进行事件注册created () {this.$on('visible', val => {this.visible = val;});}
};
</script>

对于dropdown组件,则是通过watch visible属性,如果visible属性发生改变则将visible属性的最新值传递给dropdown-menu组件并触发其回调。而对于visible属性的控制,具体如下

<template>
<div class="v-dropdown":trigger="trigger":visible="visible":hideOnClick="hideOnClick"v-clickoutside="hide"
><slot></slot>
</div>
</template><script>
// vue自带指令,点击节点以外地方,并触发回调
import Clickoutside from 'element-ui/src/utils/clickoutside';
import Emitter from 'element-ui/src/mixins/emitter';
export default {name: 'VDropdown',componentName: 'VDropdown',mixins: [Emitter],// 注册指令directives: { Clickoutside },props: {trigger: {type: String,default: 'hover'},hideOnClick: {type: Boolean,default: true}},data () {return {timeout: null,visible: false}},methods: {// 显示show () {let that = this;clearTimeout(this.timeout);this.timeout = setTimeout(function () {that.visible = true;}, 150);},// 隐藏hide () {let that = this;clearTimeout(this.timeout);this.timeout = setTimeout(function () {that.visible = false;}, 150);},// click事件的处理handleClick () {this.visible = !this.visible;},initEvent () {let {trigger, show, hide, handleClick} = this;// 触发事件的elm节点let triggerElm = this.$slots.default[0].elm;// hover事件处理if (trigger === 'hover') {triggerElm.addEventListener('mouseenter', show);triggerElm.addEventListener('mouseleave', hide);}// click事件处理else if (trigger === 'click') {triggerElm.addEventListener('click', handleClick);}}},watch: {// 向下传递,即VDropdownMenu组件传递visible属性并触发其回调visible (val) {this.broadcast('VDropdownMenu', 'visible', val);}},mounted () {this.initEvent();}
};
</script>

写到这里dropdown-menu的消失与显示的功能则已实现。

b、dropdown-list点击事件command指令的实现

在这里,我们需要实现的则是对于dropdown-list组件的拓展功能的实现,试想,如果我需要在点击dropdown-list的时候做一些自定义的事件,该如何实现呢?那么接下来我们要做的就是给人提供一个对外的指令接口command,$emit监测到command指令的时候触发其自定义的事件回调。

首先我们看看dropdown-list进行的操作,具体如下

<template>
<li class="v-dropdown-menu_list"@click="handleClick":command="command"
><slot></slot>
</li>
</template><script>
import Emitter from 'element-ui/src/mixins/emitter';
export default {name: 'VDropdownList',mixins: [Emitter],props: {command: String},methods: {// 点击dropdown-list时,向上传递,即监听VDropdown的 menu-list-click自定义事件并触发其回调handleClick (e) {this.dispatch('VDropdown', 'menu-list-click', [this.command, this]);}}
};
</script>

对于dropdown组件中,需要做的事情便是在组件渲染完成后通过$on注册 'menu-list-click'事件,如下

this.$on('menu-list-click', this.handleMenuListClick);

需要被触发的回调如下

handleMenuListClick (command, instance) {// 点击list后是否隐藏menu,属性通过hideOnClick控制if (this.hideOnClick) {this.visible = false;}// 监听command指令,并触发其回调this.$emit('command', command, instance);
}

调用如下

<template><v-dropdown trigger="click" @command="commandHandle" :hide-on-click="false"><span class="drop-down_link">下拉菜单</span><v-dropdown-menu><v-dropdown-list command="a">下拉列表1</v-dropdown-list><v-dropdown-list command="b">下拉列表2</v-dropdown-list><v-dropdown-list command="c"><h4>下拉列表3</h4></v-dropdown-list></v-dropdown-menu></v-dropdown>
</template>
<script>
export default {methods: {commandHandle(command) {console.log(command);}}
}
</script>

执行结果如下(点击每个列表)
193138_Xp2u_2912341.png

到这里,点击dropdown-list触发的事件回调也就完成了。我们需要完成的属于自己的dropdown组件也算是完成了。

三、vue部分源码解析

我们看到上面的代码可以看出,对于组件之间的消息与事件的传递我们是通过$on,$emit完成的。当然我们看文档还知道,vue还提供了$once$off的实例方法(API链接:https://vuejs.org/v2/api/#vm-on)。那么对于$on$once$off$emit,vue的作者又是如何实现的呢。

其实从上面$on,$emit实现的功能来看,我们就能看出,对于$on,他就像一个发布者,只负责发布消息。而$emit则相当于订阅者,监听发布者发布的消息。而$once则只发布一次消息,$off则取消发布的消息。想要了解观察者模式(发布-订阅者模式)的小伙伴请先转链接http://www.sxrczx.com/docs/js/2355128.html。

下面我将直接将源码及我写好的注释放给大家,具体如下

var hookRE = /^hook:/;
/*** [$on 事件注册]* @param  {[type]}   event [注册事件别名]* @param  {Function} fn    [注册事件对应回调]*/
Vue.prototype.$on = function (event, fn) {var this$1 = this;var vm = this;// 遍历需要发布的消息是否是数组,如果是,则循环注册if (Array.isArray(event)) {for (var i = 0, l = event.length; i < l; i++) {this$1.$on(event[i], fn);}// 如果不是则单次注册} else {// 默认值 vm._events = Object.create(null); 通过数组的push()将注册事件回调保存在vm._events[event]中(vm._events[event] || (vm._events[event] = [])).push(fn);if (hookRE.test(event)) {// 默认值vm._hasHookEvent = falsevm._hasHookEvent = true;}}return vm
};
/*** [$once 仅注册一次事件]* @param  {[type]}   event [注册事件别名]* @param  {Function} fn    [注册事件对应回调]*/
Vue.prototype.$once = function (event, fn) {var vm = this;// 定义 on()函数进行事件监听并移除,同时作为$on() 函数的回调执行function on () {// 移除事件vm.$off(event, on);// 执行回调,进行事件监听fn.apply(vm, arguments);}on.fn = fn;vm.$on(event, on);return vm
};
/*** [$off 事件移除]* @param  {[type]}   event [注册事件别名]* @param  {Function} fn    [注册事件对应回调]*/
Vue.prototype.$off = function (event, fn) {var this$1 = this;var vm = this;// 移除所有的事件监听器if (!arguments.length) {vm._events = Object.create(null);return vm}// 如果事件别名是数组,则循环将数组中对应的所有事件别名对应的监听器移除if (Array.isArray(event)) {for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {this$1.$off(event[i$1], fn);}return vm}// specific eventvar cbs = vm._events[event];if (!cbs) {return vm}// 如果只传了事件别名一个参数,则移除该事件对应的所有监听器if (arguments.length === 1) {vm._events[event] = null;return vm}// 参数中既传了事件别名,还传了回调var cb;var i = cbs.length;// 遍历事件对应的所有监听器,即 cbs = vm._events[event]while (i--) {cb = cbs[i];// 如果找到目标监听器,则通过splice移除数组中的监听器,并通过break终止循环if (cb === fn || cb.fn === fn) {cbs.splice(i, 1);break}}return vm
};
/*** [$emit 触发事件]* @param  {[type]} event [事件别名]*/
Vue.prototype.$emit = function (event) {var vm = this;{var lowerCaseEvent = event.toLowerCase();if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {tip("Event \"" + lowerCaseEvent + "\" is emitted in component " +(formatComponentName(vm)) + " but the handler is registered for \"" + event + "\". " +"Note that HTML attributes are case-insensitive and you cannot use " +"v-on to listen to camelCase events when using in-DOM templates. " +"You should probably use \"" + (hyphenate(event)) + "\" instead of \"" + event + "\".");}}// 定义cbs接收 vm._events[event]var cbs = vm._events[event];if (cbs) {// 通过判断cbs缓存的监听器个数,确保cbs为数组,以便下面的循环执行cbs = cbs.length > 1 ? toArray(cbs) : cbs;var args = toArray(arguments, 1);// 遍历数组cbs,循环执行数组cbs中的方法for (var i = 0, l = cbs.length; i < l; i++) {cbs[i].apply(vm, args);}}return vm
};

 

OK,到这里,这篇博客也该谢幕了,相信看到这里,小伙伴们应该也能写出属于自己的element-ui组件,并且理解了vue是如何进行事件的注册,及事件的回调触发的。对于博客中我实现的dropdown组件,后期我会做下整理并上传到github。小伙伴们关注走一波,后续动弹更精彩哦!


本文作者:qiangdada
本文发布时间:2017/04/30
本文来自云栖社区合作伙伴 开源中国,了解相关信息可以关注oschina.net网站。

这篇关于合格前端系列第二弹-Vue组件开发续篇的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/170020

相关文章

部署Vue项目到服务器后404错误的原因及解决方案

《部署Vue项目到服务器后404错误的原因及解决方案》文章介绍了Vue项目部署步骤以及404错误的解决方案,部署步骤包括构建项目、上传文件、配置Web服务器、重启Nginx和访问域名,404错误通常是... 目录一、vue项目部署步骤二、404错误原因及解决方案错误场景原因分析解决方案一、Vue项目部署步骤

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

前端原生js实现拖拽排课效果实例

《前端原生js实现拖拽排课效果实例》:本文主要介绍如何实现一个简单的课程表拖拽功能,通过HTML、CSS和JavaScript的配合,我们实现了课程项的拖拽、放置和显示功能,文中通过实例代码介绍的... 目录1. 效果展示2. 效果分析2.1 关键点2.2 实现方法3. 代码实现3.1 html部分3.2

CSS弹性布局常用设置方式

《CSS弹性布局常用设置方式》文章总结了CSS布局与样式的常用属性和技巧,包括视口单位、弹性盒子布局、浮动元素、背景和边框样式、文本和阴影效果、溢出隐藏、定位以及背景渐变等,通过这些技巧,可以实现复杂... 一、单位元素vm 1vm 为视口的1%vh 视口高的1%vmin 参照长边vmax 参照长边re

CSS3中使用flex和grid实现等高元素布局的示例代码

《CSS3中使用flex和grid实现等高元素布局的示例代码》:本文主要介绍了使用CSS3中的Flexbox和Grid布局实现等高元素布局的方法,通过简单的两列实现、每行放置3列以及全部代码的展示,展示了这两种布局方式的实现细节和效果,详细内容请阅读本文,希望能对你有所帮助... 过往的实现方法是使用浮动加

css渐变色背景|<gradient示例详解

《css渐变色背景|<gradient示例详解》CSS渐变是一种从一种颜色平滑过渡到另一种颜色的效果,可以作为元素的背景,它包括线性渐变、径向渐变和锥形渐变,本文介绍css渐变色背景|<gradien... 使用渐变色作为背景可以直接将渐China编程变色用作元素的背景,可以看做是一种特殊的背景图片。(是作为背

基于Python开发PPTX压缩工具

《基于Python开发PPTX压缩工具》在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,不便于传输和存储,所以本文将使用Python开发一个PPTX压缩工具,需要的可以了解下... 目录引言全部代码环境准备代码结构代码实现运行结果引言在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,

CSS自定义浏览器滚动条样式完整代码

《CSS自定义浏览器滚动条样式完整代码》:本文主要介绍了如何使用CSS自定义浏览器滚动条的样式,包括隐藏滚动条的角落、设置滚动条的基本样式、轨道样式和滑块样式,并提供了完整的CSS代码示例,通过这些技巧,你可以为你的网站添加个性化的滚动条样式,从而提升用户体验,详细内容请阅读本文,希望能对你有所帮助...

css实现图片旋转功能

《css实现图片旋转功能》:本文主要介绍了四种CSS变换效果:图片旋转90度、水平翻转、垂直翻转,并附带了相应的代码示例,详细内容请阅读本文,希望能对你有所帮助... 一 css实现图片旋转90度.icon{ -moz-transform:rotate(-90deg); -webkit-transfo

vue基于ElementUI动态设置表格高度的3种方法

《vue基于ElementUI动态设置表格高度的3种方法》ElementUI+vue动态设置表格高度的几种方法,抛砖引玉,还有其它方法动态设置表格高度,大家可以开动脑筋... 方法一、css + js的形式这个方法需要在表格外层设置一个div,原理是将表格的高度设置成外层div的高度,所以外层的div需要