前言
最近在使用 Vue+TypeScript 鼓捣自己的组件库,期间参考不少(抄????)element,iview的源码。发现了一些常用的功能的背后,往往是复杂的实现。于是准备写一系列文章,介绍这些组件背后的原理。今天是第一篇,手把手带你实现 Notice 组件。
API 设计
通常我们在使用 iview 或者 element 的 Notice 组件的时候,都是通过调用挂载到 Vue 原型链上的方法的形式。如下图所示。
我们的组件的调用方式,也参考类似的设计, 不同的是我们新添加了一个 APIsetLen。代码如下
这是因为楼主曾经接过一个需求,在做公司一款 toC 的时候,产品不希望屏幕上出现太多的通知框,而是希望一次最多只出现 3 个。所有楼主在设计组件的时候,将这个定制化的需求也添加了上去。
因为我们需要实现,屏幕上只显示指定数量的 Notice 通知框,所以我们使用两个数组,保存 Notice 的实例。queue 队列用来存储全部的 Notice 实例,showQueue 用来屏幕上显示的 Notice 实例。
$Notice,方法用来想 queue 添加了一个 Notice 的实例; processQueue 方法则用来处理 queue 队列; remove 方法删除特定的 Notice; clear 方法则用来清除全部的 Notice。setLen 用来设定同屏显示 Notice 的数量。len 属性则是同屏的最大数量。
模版设计
模版设计没啥好说的,常规布局。其中 Icon 组件,是我之前写的图标组件。showClose,控制是否显示 Icon 图标。visible 控制 Notice 的显隐。
方法设计
$Notice
使用 Vue.extend 方法构建 NoticeConstructor,NoticeConstructor 是 Vue 的子类。NoticeConstructor 的实例,可以使用 $mount 方法生成 DOM,然后手动或者指定 $mount 的参数,将 DOM 渲染到页面之中。
在 $Notice 方法的内部,使用 uuid, 生成一个唯一 id,这个唯一的标记,将会帮助我们查找队列中指定的 notice 对象。紧接着我们会对 onClose 方法进行一层包装。**onClose 将会在每次关闭 notice 的时候调用,onClose 在内部调用 $Notice.remove 方法,$Notice.remove 方法会将指定的 id 所对应的 notice 对象移除出队列 **。
接着我们将创建 notice 的实例,并将其 push 到 queue 队列中,接着调用 $Notice.processQueue 方法处理 queue 队列。
关于 uuid 这个方法,uuid 这个方法生成的并不是真的唯一 id,而是一个重复概率很低的 id。重复概率大概是 1ms 内,1 亿多分之 1 吧。这个是我在 stackoverflow 上找的方法,代码如下。
$Notice.processQueue
在 processQueue 方法中,我们首先判断 showQueue 队列是不是 满的。如果不是,我们将会从 queue 队列的头部截取一个 notice 对象。使用 $mount 方法,生成 DOM 并 append 到 body 中。
因为 notice 在页面上的样式,是自上而下的,所以我们将会计算 notice 的相对顶部的偏移量,每一个 notice 对象的自身高度和 15px 的间距。
同时,我们会将 notice 的 visible 属性设置为 true,这会触发我们的 transition 动画,并将这个 notice 对象 push 到 showQueue 队列中。
生命周期 mounted
接着我们将目光转移到 Notice 组件内部,我们将 notice,append 到 DOM 中后。我们会在 mounted 函数中起一个定时器,定时器将会等待指定的 duration 毫秒,duration 是我们指定 notice 存在的时间,如果 duration 为 0,notice 将会永远存在。
duration 毫秒之后,将会执行 notice 组件内部的 close 方法。
close
在 close 方法中,我们会为当前组件添加 transitionend,事件。这个事件将会在 css 动画( 离场动画)结束后触发。我们将 visible 设置为 false 这会触发,组件的离场动画。接着我们调用 onClose 方法,这会处理我们的队列。
$Notice.remove
之前我们对 onClose 进行了一层包装,调用 onClose 方法,会调用我们的 $Notice.remove 方法。
在 $Notice.remove 方法中,我们将会通过 id 找到需要移除的 notice 对象,将其移除出 showQueue 队列,接着循环剩下的 showQueue 队列,将它们 style.top 向上移动。最后我们继续调用 $Notice.processQueue 方法,从 queue 队列中,拉取新的 notice 对象,push 到 showQueue 队列中。
destroy
当离场动画执行完毕后,transitionend 回调会调用 destroy 方法。
destroy 将会主动卸载我们的组件,并从 DOM 中移除我们的元素。notice 对象的生命周期至此结束。
$Notice.clear & $Notice.setLen
clear 和 setLen 相对而言比较简单,这里就不再赘述了。
后续
- 「组件」设计一款 Input 组件
- 「组件」设计一款 Grid 组件
- 「组件」设计一款 Button 组件
- 「组件」设计一款 Collapse 组件
- 「组件」设计一款 Icon 组件
- 「组件」设计一款 Select 组件
- 「组件」设计一款 Autocomplete 组件
…..
本系列的文章,尽量做到短小精悍。Select,Table,DatePicker 组件将会难点。
明天可能会更新一篇 React Hook 的学习文章。因为报名了周末晚上的公开课介绍 Hook 的原理。下周想重新阅读下 preact 的源码,学习学习 preact 中 hook 的实现原理。
参考
- iview 源码
- element 源码