Vue做为最近最煊赫一时的前端框架,其简单的入门方式和功能强大的API是其优势。而同时由于其API的多样性和丰富性,因此他的不少开发方式就和一切基于组件的React不一样,若是没有对Vue的API(有一些甚至文档都没提到)有一个全面的了解,那么在开发和设计一个组件的时候有可能就会绕一个大圈子,因此我很是推荐各位在学习Vue的时候先要对Vue核心的全部API都有一个了解。这篇文章我会从实践出发,遇到一些知识点会顺带总结一下。文章很长,一次看不完能够先收藏,若是你刚入门vue,那么相信这篇文章对你之后的提高绝对有帮助
进入正题,我相信不论什么项目几乎都会有一个必不可少的功能,就是用户操做反馈、或者提醒.像这样(简单的一个demo)css
其实在vue的中大型项目中,这些相似的小功能会更加丰富以及严谨,而在以Vue做为核心框架的前端项目中,由于Vue自己是一个组件化和虚拟Dom的框架,要实现一个通知组件的展现固然是很是简单的。但由于通知组件的使用特性,直接在模板当中书写组件并经过v-show或者props控制通知组件的显示显然是很是不方便的而且这样意味着你的代码结构要变,当各类各样的弹层变多的时候,咱们都将其挂载到APP或者一个组件下显然不太合理,并且若是要在action或者其余非组件场景中要用到通知,那么纯组件模式的用法也没法实现。那么有没有办法即用到Vue组件化特性方便得实现一个通知组件的展示,那么咱们能否用一个方法来控制弹层组件的显示和隐藏呢?前端
目标一
实现一个简单的反馈通知,能够经过方法在组件内直接调用。好比Vue.$confirm({...obj})vue
首先,咱们来实现通知组件,相信这个大部分人都能写出来一个像模像样的组件,不啰嗦,直接上代码编程
<template> <div :class="type" class="eqc-notifier"> <i :class="iconClass" class="icon fl"/> <span>{{ msg }}</span> <!-- <span class="close fr eqf-no" @click="close"></span> --> </div> </template> <script> export default { name: 'Notification', props: { type: { type: String, default: '' }, msg: { type: String, default: '' } }, computed: { iconClass() { switch (this.type) { case 'success': return 'eqf-info-f' case 'fail': return 'eqf-no-f' case 'info': return 'eqf-info-f' case 'warn': return 'eqf-alert-f' } } }, mounted() { setTimeout(() => this.close(), 4000) }, methods: { close() { } } } </script> <style lang="scss"> .eqc-notifier { position: fixed; top: 68px; left: 50%; height: 36px; padding-right: 10px; line-height: 36px; box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.16); border-radius: 3px; background: #fff; z-index: 100; // 层级最高 transform: translateX(-50%); animation: fade-in 0.3s; .icon { margin: 10px; font-size: 16px; } .close { margin: 8px; font-size: 20px; color: #666; transition: all 0.3s; cursor: pointer; &:hover { color: #ff296a; } } &.success { color: #1bc7b1; } &.fail { color: #ff296a; } &.info { color: #1593ff; } &.warn { color: #f89300; } &.close { animation: fade-out 0.3s; } } </style>
在这里须要注意,咱们定义了一个close方法,但内容是空的,虽然在模板上有用到,可是彷佛没什么意义,在后面咱们要扩展组件的时候我会讲到为何要这么作。数组
建立完这个组件以后,咱们就能够在模板中使用了<notification type="xxx" msg="xxx" /> promise
实现经过方法调用该通知组件
其实在实现经过方法调用以前,咱们须要扩展一下这个组件,由于仅仅这些属性,并不够咱们使用。在使用方法调用的时候,咱们须要考虑一下几个问题:浏览器
在这个前提下,咱们须要扩展该组件,可是扩展的这些属性不能直接放在原组件内,由于这些可能会影响组件在模板内的使用,那怎么办呢?这时候咱们就要用到Vue里面很是好用的一个API,extend,经过他去继承原组件的属性并扩展他。前端框架
来看代码app
import Notifier from './Notifier.vue' function install(Vue) { Vue.notifier = Vue.prototype.notifier = { success, fail, info, warn } } function open(type, msg) { let UiNotifier = Vue.extend(Notifier) let vm = new UiNotifier({ propsData: { type, msg }, methods: { close: function () { let dialog = this.$el dialog.addEventListener('animationend', () => { document.body.removeChild(dialog) this.$destroy() }) dialog.className = `${this.type} eqc-notifier close` dialog = null } } }).$mount() document.body.appendChild(vm.$el) } function success(msg) { open('success', msg) } function fail(msg) { open('fail', msg) } function info(msg) { open('info', msg) } function warn(msg) { open('warn', msg) } Vue.use(install) export default install
能够看到close方法在这里被实现了,那么为何要在原组件上面加上那些方法的定义呢?由于须要在模板上绑定,而模板是没法extend的,只能覆盖,若是要覆盖从新实现,那扩展的意义就不是很大了。其实这里只是一个消息弹窗组件,是能够在模板中就被实现,还有插件怎么注入,你们均可以本身抉择。框架
同时在使用extend的时候要注意:
方法和属性的定义是直接覆盖的
首先经过 let UiNotifier = Vue.extend(Notifier),咱们获得了一个相似于Vue的子类,接着就能够经过new UiNotifier({...options})的方式去建立Vue的实例了,同时经过该方式建立的实例,会有组件定义里面的全部属性。
在建立实例以后,vm.$mount()手动将组件挂载到DOM上面,这样咱们能够不依赖Vue组件树来输出DOM片断,达到自由显示通知的效果。
扩展:
(
说一下$mount,咱们也许不少项目的主文件是这样的
new Vue({ router, store, el: '#app', render: h => h(App) })
其实el与$mount在使用效果上没有任何区别,都是为了将实例化后的vue挂载到指定的dom元素中。若是在实例化vue的时候指定el,则该vue将会渲染在此el对应的dom中,反之,若没有指定el,则vue实例会处于一种“未挂载”的状态,此时能够经过$mount来手动执行挂载。值得注意的是若是$mount没有提供参数,模板将被渲染为文档以外的的元素,而且你必须使用原生DOM API把它插入文档中,因此我上面写的你应该明白了吧!
这是$mount的一个源码片断,其实$mount的方法支持传入2个参数的,第一个是 el,它表示挂载的元素,能够是字符串,也能够是 DOM 对象,若是是字符串在浏览器环境下会调用 query 方法转换成 DOM 对象的。第二个参数是和服务端渲染相关,在浏览器环境下不须要传第二个参数。
)
好了,咱们如今其实就能够在组件中:
this.notifier[state](msg)来调用了,是否是很方便?
进阶
咱们刚才实现了在Vue中经过方法来进行用户反馈的提醒,再增长一个难度:
咱们vue项目中应该也遇到过这种状况,弹出一个对话框或是选择框?不但要求用方法弹出,而且能接收到对话框交互所返回的结果。
这里就不详细的分析了直接上代码说(以前的代码,用render来写的组件,懒得改了,直接拿来用...),先建立一个对话框组件---Confirm.vue
<script> let __this = null export default { name: 'Confirm', data() { return { config: { msg: '', ifBtn: '', top: null } } }, created() { __this = this }, methods: { createBox(h) { let config = {} config.attrs = { id: '__confirm' } let children = [] children.push(this.createContainer(h)) children.push(this.createBg(h)) return h('div', config, children) }, createBg(h) { return h('div', { class: 'bg', on: { click: __this.$cancel } }) }, createContainer(h) { let config = {} config.class = { 'box-container': true } if (__this.config.top) { config.style = { 'top': __this.config.top + 'px', 'transform': 'translate(-50%, 0)' } } let children = [] children.push(this.createContentBox(h)) children.push(this.createClose(h)) if (__this.config.ifBtn) { children.push(__this.createBtnBox(h)) } return h('div', config, children) }, createContentBox(h) { let config = {} config.class = { 'content-box': true } return h('div', config, [__this.createContent(h)]) }, createContent(h) { let config = {} config.domProps = { innerHTML: __this.config.msg } return h('p', config) }, createClose(h) { return h('i', { class: 'eqf-no pointer close-btn', on: { click: __this.$cancel } }) }, createBtnBox(h) { return h( 'div', { class: { 'btn-box': true } }, [ __this.createBtn(h, 'btn-cancel middle mr10', '取消', __this.$cancel), __this.createBtn(h, 'btn-primary middle mr10', '肯定', __this.$confirm) ]) }, createBtn(h, styles, content, callBack) { return h('button', { class: styles, on: { click: callBack } }, content) } }, render(h) { return this.createBox(h) } } </script> <style scoped> #__confirm { position: fixed; top: 0; left: 0; z-index: 10; width: 100%; height: 100%; } #__confirm .bg { position: fixed; top: 0; left: 0; z-index: 0; width: 100%; height: 100%; } #__confirm .box-container { position: absolute; width: 500px; padding: 20px; padding-top: 30px; border-radius: 3px; background: #fff; z-index: 1; box-shadow: 2px 2px 10px rgba(0,0,0,0.4); top: 50%; left: 50%; transform: translate(-50%, -50%); } #__confirm .content-box { font-size: 14px; line-height: 20px; margin-bottom: 10px; } #__confirm .btn-box { margin-top: 20px; text-align: right; } #__confirm .close-btn { position: absolute; top: 15px; right: 20px; font-size: 16px; color: #666666; } #__confirm .close-btn:hover { color: #1593FF; } #__confirm .bg { position: fixed; } </style>
而后建立confirm.js
'use strict' import Confirm from './Confirm.vue' const confirmConstructor = Vue.extend(Confirm) const ConfirmViewStyle = config => { const confirmInstance = new confirmConstructor({ data() { return { config } } }) confirmInstance.vm = confirmInstance.$mount() confirmInstance.dom = confirmInstance.vm.$el document.body.appendChild(confirmInstance.dom) } const close = () => { let dom = document.querySelector('body .modelServe-container') dom && dom.remove() Vue.prototype.$receive = null } const closeConfirm = () => { let dom = document.getElementById('__confirm') dom && dom.remove() Vue.prototype.$confirm = null } function install(Vue) { Vue.prototype.modelServe = { confirm: (obj) => { return new Promise(resolve => { Vue.prototype.$confirm = (data) => { resolve(data) closeConfirm() } ConfirmViewStyle(obj) }) } } Vue.prototype.$dismiss = close Vue.prototype.$cancel = closeConfirm } Vue.use(install) export default install
思路很简单,在咱们建立的时候同时返回一个promise,同时将resolve通行证暴露给vue的一个全局方法也就是将控制权暴露给外部,这样咱们就能够向这样,我上面的confiram.vue是直接把取消绑定成了$cancel,把肯定绑定成了$confirm,因此点击肯定会进入full,也就是.then中,固然你也能够传参数
this.modelServe.confirm({ msg: '返回后数据不会被保存,确认?', ifBtn: true }).then(_ => { this.goBack() }).catch()
写的有点多,其实还能够扩展出好多技巧,好比模态框中传一个完整的组件,并展现出来,简单地写一下,其实只需改动一点
import Model from './Model.vue' const modelConstructor = Vue.extend(Model) const modelViewStyle = (obj) => { let component = obj.component const modelViewInstance = new modelConstructor({ data() { return { disabledClick: obj.stopClick // 是否禁止点击遮罩层关闭 } } }) let app = document.getElementById('container') modelViewInstance.vm = modelViewInstance.$mount() modelViewInstance.dom = modelViewInstance.vm.$el app.appendChild(modelViewInstance.dom) new Vue({ el: '#__model__', mixins: [component], data() { return { serveObj: obj.obj } } }) } ... Vue.prototype.modelServe = { open: (obj) => { return new Promise(resolve => { modelViewStyle(obj, resolve) Vue.prototype.$receive = (data) => { resolve(data) close() } }) } }
调用:
sendCallBack() { this.modelServe.open({ component: AddCallback, stopClick: true }).then(data => if (data === 1) { this.addInit() } else { this.goBack() } })
},
这里咱们用了mixins,最后最后再简单地介绍一下mixins,extend,extends的区别
**- Vue.extend使用基础 Vue 构造器,建立一个“子类”。参数是一个包含组件选项的对象。
归纳
extend用于建立vue实例 mixins能够混入多个mixin,extends只能继承一个 mixins相似于面向切面的编程(AOP),extends相似于面向对象的编程 优先级Vue.extend>extends>mixins
总结 到这里,关于如何实现经过方法调用一个Vue组件内容以及用到的一些API以及原理就差很少了,代码若有不懂得地方能够随时提问,欢迎交流。