此次的教程里,咱们要把组件化进行到底!最近半年的几个项目中,都遇到了须要使用Toast或者Notification组件的状况。在目前已有的一些基于Vue.js开发的组件库,都没有找到太合适的,因此本身重头实现了一个。历经几个项目的磨练,这个提示组件的功能已经愈来愈完善,此次就分享一下组件以及其实现思路吧。css
GitHub 仓库:https://github.com/Yuyz0112/vue-notie
Demo 地址:http://lab.myriptide.com/vue-notie/vue
Vue.js的组件化能够说是其招牌特性之一,而在实际应用时,并不是一味地追求组件颗粒越小越好,而是须要根据项目的实际需求,来分析本身须要什么级别的组件。git
例如在一个SPA中,我可能有主页、文章列表页、文章页、我的中心页4个主要的视图,因而我将其分别对应的写成4个组件。github
可是在实际编写的过程当中,发现他们共用了同一套侧边栏,而侧边栏对应的代码也在4个组件中重复书写了4次。因此能够将侧边栏单独写成一个组件进行复用。web
以后,咱们可能发现能够复用的还有一些表单、按钮之类的内容咱们均可以复用成组件。但实际上,咱们也会发现过分的组件化会致使代码量上升、开发时间增长以及额外的数据传递等等。因此若是不打算制做一个完整的组件库,那么在实际项目中作到按需拆分、整合便可,不用过度的追求每一个可复用的部分都写成单个组件。vuex
由于alert大部分时间不能知足咱们的需求啊。每每项目里须要一个相似于alert的东西,用美观、可定制的方式提示用户一些信息,所以这样一个提示组件颇有必要。数组
同时,咱们也不但愿同一时间出现多个提示混淆用户,所以在设计上,咱们将提示组件设定为具备惟一性,整个应用中各个视图调用的都是同一个提示组件。sass
接下来,由简入繁依次实现提示组件的各个功能。app
最基本的功能固然是触发后显示,而且可以以某种方式关闭。惟一须要自定义的部分,就是具体显示的内容。因此最开始组件长这样:ide
<template> <div class="notification fixed" v-if="show" transition="slide"> <div class="delete" @click="close()"></div> <div class="content"> {{ options.content }} </div> </div> </template> <script> export default { props: { options: { type: Object, default: () => { return {} } }, show: { type: Boolean, default: false } }, methods: { close () { this.show = false this.options = {} } } } </script> <style scoped lang="sass"> .slide-transition transition: all .3s ease transform: translate3d(0, 0, 0) .slide-enter, .slide-leave transform: translate3d(0, -100%, 0) .delete -moz-appearance: none -webkit-appearance: none background: rgba(51,51,51,0.2) cursor: pointer display: inline-block height: 24px position: relative vertical-align: top width: 24px float: right &:before, &:after background: #fff content: "" display: block height: 2px left: 50% margin-left: -25% margin-top: -1px position: absolute top: 50% width: 50% &:before transform: rotate(45deg) &:after transform: rotate(-45deg) &:hover background: rgba(51,51,51,0.5) .notification width: 100% line-height: 2 z-index: 3 position: fixed top: 0 left: 0 .content padding: .75rem 2rem </style>
思路很简单,props传递两个数据,show用于控制显示,options传入包括内容在内的自定义内容。为了让提示的显示更加天然,添加了一个滑动进入和离开的transition。
注意:这里的关闭按钮是经过css实现的,若是在你的项目中有对应的icon,能够将其替换掉。
在此处,也可使用slot来进行内容的传递,但考虑到以后还有别的参数须要传递至组件内,一次用一个统一的对象options进行传递。
一般提示的内容种类不少,有的是成功提示,有的是警告,有的则是报错。所以咱们须要定义不一样的样式以表达不一样的内容。
方法很简单,在options中传入背景色和文字颜色两个参数,若是组件中检测到了传入的样式参数,就用其替换默认样式。
Vue.js在处理动态样式时很是灵活,为了让代码更清晰,我没有选择将动态样式内联,而是单独使用一个计算属性setStyle进行设定:
computed: { setStyle () { return { color: this.options.textColor || '#fff', background: this.options.backgroundColor || '#21e7b6' } } }
这样一来,只要在options中一并传入textColor和backgroundColor两个属性,就能够轻松自定义提示样式了。
不少时候,咱们但愿提示在必定时间以后能够自动关闭,所以组件也须要扩展出一个自动关闭的模式。一样的,在“数据驱动”的思想下,咱们应该提供一个数据,用来代表这个提示是否自动关闭。
options中的autoClose属性就是这个做用。一样的,自动关闭的延迟时间显然也要可以自定义,所以还一同添加了showTime这一属性。
自动关闭自己不太复杂,咱们只须要使用setTimeout,定义一个计时器便可。
首先是监听提示组件的显示。
在这里,我经过watch监听options的变化来处罚计时器。因为咱们已经定义了一个close方法用于关闭计时器,而且在关闭时重置了show和options的值,因此在options变化时,只须要判断options中的autoClose是否为true,就能知道是否须要启动计时器了。这里单独使用一个countdown方法来处理定时器相关的操做。
新增代码以下:
data () { return { timers: [] } }, methods: { countdown () { if (this.options.autoClose) { const t = setTimeout(() => { this.close() }, this.options.showTime || 3000) this.timers.push(t) } } }, watch: { options () { this.timers.forEach((timer) => { window.clearTimeout(timer) }) this.timers = [] this.countdown() } }
细心地你确定会发现,这段代码中,有一些奇怪的处理。咱们定义了一个空数组timers,而且每次开始一个计时器的时候,就把计时器存入数组中,而每次options变化时,咱们也从timers中遍历全部计时器并取消,以后清空timers。
这个作法,主要是为了不一个计时器尚未结束时,又开始一个新的提示所引起的提示被提早关闭的清空。举个例子,若是没有这样的处理,那么先发出一个自动关闭的提示,在其没自动关闭以前,就再发出一个新的提示。那么第一个提示的定时器依然会错误的关闭新提示。
这样的问题主要是因为咱们全部的计时器都是在同一个组件中,本质上都是同一个提示,所以须要清除计时器,避免冲突。许多组件库中相似的功能组件,是采用每一条提示就新生成一个提示组件的方式来实现的。可是那样在多个提示连续出现时,就会出现堆叠在一块儿,又各自离开的状况。
以前的版本中,个人提示组件也采用了相似的设计方式,可是在最近的一个项目中,须要实现半透明的提示组件,就出现了堆叠后看不清提示文字的现象,才使用了如今新的模式。
紧接着,我拓展了一个自动关闭模式下的倒计时条功能。思路上没有使用Vue.js的transition系统,而是采用了Css3自己的动画系统。在一个自动关闭的提示被初始化时,为计时条添加一个样式,效果是向X轴负方向移动100%,transition时间则经过计算属性对应设定。具体实现能够参考源代码,这里很少作赘述。
最后则是让提示组件更灵活。有的时候,咱们想展现的多是能够自定义样式的文本、亦或是一个超连接甚至更多。而Vue.js实现起来不要太简单。咱们只须要将组件中用于渲染的{{ options.content }}
变为{{{ options.content }}}
便可,对于3重花括号的模板,Vue.js会将其中的HTML标签按照正常内容渲染。
如此一来,咱们就能够将任何HTML内容放入提示中了。固然必定要注意避免将用户输入的内容渲染到3重花括号的模板中,避免XSS攻击。
不少时候,咱们会把提示组件引入到App.vue这个根组件中,可是发出提示的多是组件树中的任何一个组件。若是不想代码中遍及各类dispatch和broadcast,那么引入vuex来进行管理是个很好的方案。
大体的思路以下:
// store.js const state = { show: false, options: { autoClose: false, content: 'notice content' } } const mutations = { NEW_NOTICE (state, options) { state.show = true state.options = options }, CLOSE_NOTICE (state) { state.show = false state.options = {} } } // actions.js export const newNotice = ({dispatch}, options) => { dispatch('NEW_NOTICE', options) } export const closeNotice = ({dispatch}) => { dispatch('CLOSE_NOTICE') } // Notification.vue vuex: { getters: { show: state => state.show options: state => state.options }, actions: { close: closeNotice } } // 任意调用notice的组件 vuex: { actions: { notice: newNotice } }
引入vuex后,按上述代码进行配置,就能够在任意一处组件中,使用this.notice({options})
传递数据。不过因为vuex的单项数据流动特性,全部对state数据的操做都必须通过actions调用mutations实现,包括提示组件中的close方法也要替换成actions中的closeNotice方法。
经过这个提示组件,咱们更熟练的掌握了Vue.js的组件系统、数据传递、计算属性、transition动画等特性。另外此组件已经能够直接用于生产环境中,欢迎star、fork、pr。