精通React/Vue系列之手把手带你实现一个功能强大的通知提醒框(Notification)

前言

本文是笔者写组件设计的第十篇文章, 今天带大家实现一个比较特殊的组件——通知提醒框(Notification)。 该组件在诸如Antd或者elementUI等第三方组件库中也都会出现,主要用来为用户提供系统通知信息的.我们在调用它时并不像其他组件一样,通过引入组件标签来调用。比如Modal组件,我们一般这样来调用:

1<Modal title="xui基础弹窗" centered mask={false} visible={false}> 2 <p>我是弹窗内容</p> 3 <p>我是弹窗内容</p> 4 <p>我是弹窗内容</p> 5 <p>我是弹窗内容</p> 6</Modal>

但是通知提醒框(Notification),大多数场景下是使用js API的方式调用:

1notification.open({ 2 message: '趣谈前端React', 3 description: '学前端,学React/vue/Node,快快加入我们吧' 4});

我们看到的组件效果可能是这样的:

那么我们如何实现这样的调用方式呢?不用急,接下来笔者会一步步教你实现。

先来巩固以下组件的分类法:

  • 通用型组件: 比如Button, Icon等.
  • 布局型组件: 比如Grid, Layout布局等.
  • 导航型组件: 比如面包屑Breadcrumb, 下拉菜单Dropdown, 菜单Menu等.
  • 数据录入型组件: 比如form表单, Switch开关, Upload文件上传等.
  • 数据展示型组件: 比如Avator头像, Table表格, List列表等.
  • 反馈型组件: 比如Progress进度条, Drawer抽屉, Modal对话框等.
  • 其他业务类型

熟悉以上分类法是设计任何组件系统的前提,不管你是从零到一开发前端团队的UI库,还是基于已有组件库二次开发业务组件,以上分类法则同样适用。

本文将会使用React来开发该组件,也会使用到Javascript中常用的一些设计模式,比如单例模式,但是不管你使用什么框架来实现,原理都是通用的,如果感兴趣的朋友可以用vue也实现以一下。如果对设计模式不是很了解,可以移步:

15分钟带你了解前端工程师必知的javascript设计模式(附详细思维导图和源码).

正文

在开始组件设计之前希望大家对css3和js有一定的基础,并了解基本的react/vue语法.我们先来解构一下Notification组件, 一个Notification分为以下几个部分: 每一个区块都可以自定义配置, 也可以组合其他组件.并且我们可以配置提醒框出现的位置,就像antd的组件一样,我们有左上,左下,右上,右下这几个位置可以配置,也可以配置基于这几个位置的偏移量。并且我们都知道,antd或者element这种组件库,会自带一些主题状态,来提高用户的使用效率,比如会有success(成功状态),warning(警告状态),error(错误状态),info(通知状态)等,那么我们自己实现的组件也因该具备这些功能。

以下是笔者使用React实现后的Notification组件效果:

接下来我们来看看通知提醒框(Notification)的具体设计思路。

1. Notification组件设计思路

按照之前笔者总结的组件设计原则,我们第一步是要确认需求. 通知提醒框(Notification)组件一般会有如下需求点:

  • 能控制Notification自动关闭的时间
  • 能配置Notification渲染节点的输出位置
  • 能控制Notification的弹出位置
  • 能自定义关闭图标
  • 可以手动选择通知窗类型
  • 能自定义通知框的偏移量
  • 能设置通知框的信息和提示文本
  • 能自定义通知框的Icon
  • 通知框点击时提供回调函数
  • 通知框关闭时提供回调函数
  • 能手动销毁通知框

需求收集好之后,作为一个有追求的程序员, 会得出如下线框图: 其实通知提醒框要考虑的东西挺多的,所以在设计组件之前,一定要想理清需求和功能划分,这样才能有条不絮的去实现它,和我们实现一个复杂系统是一样的,一个组件就是一个小系统。

2. 基于react实现一个通知提醒框(Notification)

通知框的API调用实现思路其实就是通过jsx动态渲染约定好的标签,然后通过ReactDom的Render API将dom渲染到指定容器内挂在到页面,其中要想实现Notification.info这样的方式还需要考虑到创建实例的问题,我们应该使用单例模式来控制实例的创建个数。伪代码如下:

1const xNotification = (function() { 2 let notification = null; 3 if(notification) { 4 return { 5 render(dom){}, 6 config(config){}, 7 info(config){}, 8 error(config){} 9 // ... 10 } 11 }else { 12 notification = new Notification({}) 13 return { 14 render(dom){}, 15 config(config){}, 16 info(config){}, 17 error(config){} 18 // ... 19 } 20 } 21})() 22// 使用 23xNotification.info({...}) 24xNotification.error({...})

但是真正要实现以上需求讨论的那些通知框的功能,实际上我们还是要写很多代码来处理不同的情况的,所以为了方便大家理解,我们这里使用React Notification这个第三方库来帮我们处理基本的逻辑,笔者会基于它,来实现上面我们讨论的那些功能。

2.1 搭建通知提醒框(Notification)的基本骨架

首先按照笔者的代码风格,一般会考虑组件设计的框架,然后再一步步往里面填充内容和逻辑。通过这种渐进式的设计思路,能让我们逻辑更严谨,更清晰。具体代码如下:

1import Notification from 'rc-notification' 2import './index.less' 3 4const xNotification = (function() { 5 let notification = null 6 /** 7 * notice类型弹窗 8 * @param {config} object 通知框配置属性 9 * @param {type} string 通知窗类型 10 * @param {btn} ReactNode 自定义关闭按钮 11 * @param {bottom} number 消息从底部弹出时,距离底部的位置,单位像素 12 * @param {className} string 自定义 CSS class 13 * @param {description} string|ReactNode 通知提醒内容,必选 14 * @param {duration} number 默认 4.5 秒后自动关闭,配置为 null 则不自动关闭 15 * @param {getContainer} HTMLNode 配置渲染节点的输出位置 16 * @param {icon} ReactNode 自定义图标 17 * @param {key} string 当前通知唯一标志 18 * @param {message} string|ReactNode 通知提醒标题,必选 19 * @param {onClose} func 点击默认关闭按钮时触发的回调函数 20 * @param {onClick} func 点击通知时触发的回调函数 21 * @param {top} number 消息从顶部弹出时,距离顶部的位置,单位像素 22 * @param {closeIcon} ReactNode 自定义关闭图标 23 */ 24 const pop = (config) => { 25 const { 26 type, bottom, className, description, duration = 4.5, 27 getContainer = () => document.body, icon, 28 key, message, onClose, onClick, top, closable = true, closeIcon 29 } = config 30 notification.notice({ 31 content: <div className={classnames('xNotice', className )}> 32 <div className={classnames('iconWrap', type)}> 33 <Icon type={iconType[type]} /> 34 </div> 35 <div> 36 <div className="xNoticeTit"> 37 { message } 38 </div> 39 <div className="xNoticeDesc"> 40 { description } 41 </div> 42 </div> 43 </div> 44 }) 45 } 46 /** 47 * 通知提示组件, 全局参数 48 * @param {bottom} number 消息从底部弹出时,距离底部的位置,单位像素, 默认24 49 * @param {duration} number 默认自动关闭延时,单位秒 50 * @param {getContainer} HTMLNode 配置渲染节点的输出位置,默认document.body 51 * @param {placement} string 弹出位置,可选 topLeft topRight bottomLeft bottomRight 52 * @param {top} number 消息从顶部弹出时,距离顶部的位置,单位像素 53 * @param {closeIcon} HTMLNode 自定义关闭图标 54 */ 55 const config = (config) => { 56 const { duration, getContainer, placement, closeIcon } = config 57 Notification.newInstance({ 58 getContainer: getContainer, 59 duration: duration || 4.5, 60 closeIcon 61 }, (notice) => notification = notice) 62 } 63 64 if(notification) { 65 return { 66 config, 67 pop 68 } 69 } 70 // 如果为创建实例,则创建默认实例 71 Notification.newInstance({}, (notice) => notification = notice) 72 return { 73 config, 74 pop 75 } 76})() 77 78export default xNotification

首先我们根据需求把一项项的属性都罗列出来。我们在全局使用的配置方法是xNotification.config(config), 在通知框实例中我们使用xNotification.pop(config)。这点和antd的使用方式有点不同,笔者是把通知框类型放到pop的config来处理了,比如说要渲染一个成功的通知框,我们可以这么做:

1xNotification.pop({type: 'success'})

antd同样的方式会这么调用:

1// antd 2Notification.info({//...})

笔者之所以会这么做是因为info,success,warning这样的状态其实dom结构完全可以复用,所以通过配置方式可以极大的减少冗余代码。

2.2 实现通知框类型type和自定义icon

笔者其实在搭建组件框架的时候已经完成了部分属性的配置,所以这里就不一一介绍了,笔者将会介绍一些比较重要的方法的实现。

通过观察我们可以知道要想实现不同的通知框类型,只需要根据类型来动态替换icon就行了。icon图标部分采用笔者已经实现的Icon组件,具体用法和antd的Icon组件类似,如果想学习如何封装属于自己的Icon组件可以参考笔者源码。

首先我们先定义一个类型和icon的映射关系:

1 const iconType = { 2 success: 'FaRegCheckCircle', 3 warning: 'FaRegMeh', 4 info: 'FaRegLightbulb', 5 error: 'FaRegTimesCircle' 6 }

这四种类型对应着不同的icon图标类型,那么我们就可以根据用户传入的类型来展示不同icon图标了:

1<div className={classnames('iconWrap', type)}> 2 <Icon type={iconType[type]} /> 3</div>

不过我们还需要考虑的一点就是如果用户传入了自定义的icon,我们理论上应该展示自定义icon,所以type因该和icon这两个属性是有联系的。还有一种情况就是如果用户即没有配置type,有没有传入icon,那么实际上是不需要显示icon的,综合考虑之后我们的代码如下:

1{ 2 (icon || ['info', 'success', 'error', 'warning'].indexOf(type) > -1) && 3 <div className={classnames('iconWrap', type)}> 4 { 5 icon ? icon : <Icon type={iconType[type]} /> 6 } 7 </div> 8}

实现效果如下图:

2.3 通知框位置placement

通知框的位置根据业务场景来看因该是全局配置,所以我们放在config方法里设置,关于如何根据用户传入的位置信息来控制Notification显示的位置,我们也可以先定义一个枚举类:

1const adapterPos = { 2 topLeft: { 3 top: '24px', 4 left: '24px' 5 }, 6 topRight: { 7 top: '24px', 8 right: '24px' 9 }, 10 bottomLeft: { 11 bottom: '24px', 12 left: '24px' 13 }, 14 bottomRight: { 15 bottom: '24px', 16 right: '24px' 17 } 18}

从上面代码可以看到我们会定义四个基础位置,默认偏移都是24px,然后我们就可以根据用处传入的placement来匹配自己的位置信息了:

1Notification.newInstance({ 2 style: {...adapterPos[placement] }, 3 // ...

上面代码可以知道位置信息我们是通过style来设置的。具体效果如下:

2.4 实现通知框动画效果

动画我们实现一个类似与antd的从右往左入场的动画,我们来改写样式如下:

1.rc-notification-fade-enter { 2 animation: moveLeft .3s; 3} 4.rc-notification-fade-leave { 5 animation: moveOutLeft .3s; 6} 7.rc-notification-fade-enter.rc-notification-fade-enter-active { 8 animation: moveLeft .3s; 9} 10.rc-notification-fade-leave.rc-notification-fade-leave-active { 11 animation-name: moveOutLeft .3s; 12} 13 14@keyframes moveOutLeft { 15 0% { 16 17 } 18 100% { 19 right: -200%; 20 } 21} 22@keyframes moveLeft { 23 0% { 24 right: -200%; 25 } 26 100% { 27 right: 0; 28 } 29}

通过以上步骤, 一个功能强大的通知提醒框(Notification)就完成了.Notification组件算是组件库中中等复杂的组件,如果不懂的可以在评论区提问,笔者看到后会第一时间解答.

2.5 使用Notification组件

我们可以通过如下方式使用它:

1<Button type="primary" onClick={ 2 () => { 3 xNotification.pop({ 4 type: 'success', 5 message: '趣谈前端学习打卡', 6 description: '前端基础,中级进阶,高级打卡,一起玩转前端,996远离你' 7 }) 8 } 9}>点我显示通知</Button>

配置全局属性:

1import { xNotification } from '@alex_xu/xui' 2 3xNotification.config({ 4 placement: 'topRight' 5})

笔者已经将实现过的组件发布到npm上了,大家如果感兴趣可以直接用npm安装后使用,方式如下:

1npm i @alex_xu/xui 2 3// 导入xui 4import { 5 Button, 6 Skeleton, 7 Empty, 8 Progress, 9 Tag, 10 Switch, 11 Drawer, 12 Badge, 13 Alert 14} from '@alex_xu/xui'

该组件库支持按需导入,我们只需要在项目里配置babel-plugin-import即可,具体配置如下:

1// .babelrc 2"plugins": [ 3 ["import", { "libraryName": "@alex_xu/xui", "style": true }] 4]

npm库截图如下:

最后

后续笔者将会继续实现

  • table(表格),
  • tooltip(工具提示条),
  • Skeleton(骨架屏),
  • Message(全局提示),
  • form(form表单),
  • switch(开关),
  • 日期/日历,
  • 二维码识别器组件

等组件, 来复盘笔者多年的组件化之旅.

如果对于react/vue组件设计原理不熟悉的,可以参考我的之前写的组件设计系列文章:

笔者已经将组件库发布到npm上了, 大家可以通过npm安装的方式体验组件.

如果想获取组件设计系列完整源码, 或者想学习更多H5游戏, webpacknodegulpcss3javascriptnodeJScanvas数据可视化等前端知识和实战,欢迎在公号《趣谈前端》加入我们的技术群一起学习讨论,共同探索前端的边界。

更多推荐

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )