精通React/Vue系列之实现一个全局提示(Message)组件

徐小夕
• 阅读 898

前言

本文是笔者写组件设计的第十一篇文章, 今天带大家实现一个同样比较特殊的组件——全局提示(Message)组件。 我们看到的组件效果可能是这样的: 精通React/Vue系列之实现一个全局提示(Message)组件 由于全局提示组件的设计原理和笔者上一篇写的精通React/Vue系列之手把手带你实现一个功能强大的通知提醒框(Notification)是类似的,区别主要是布局和配置参数,所以说细节和实现原理部分就不在这篇文章介绍了,本文主要介绍设计思路和设计的方法。

基础组件库主要按以下分类来划分

  • 通用型组件: 比如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语法.我们先来解构一下Message组件, 一个Message分为以下几个部分:

精通React/Vue系列之实现一个全局提示(Message)组件 每一个区块都可以自定义配置, 也可以组合其他组件.我们还可以配置全局提示出现在顶部的偏移量,类似于antd的组件一样。并且我们都知道,antd或者element这种组件库,会自带一些主题状态,来提高用户的使用效率,比如会有success(成功状态),warning(警告状态),error(错误状态),info(通知状态)等,那么我们自己实现的全局提示(Message)组件也因该具备这些功能。

以下是笔者使用React实现后的Message组件效果: 精通React/Vue系列之实现一个全局提示(Message)组件

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

1. Message组件设计思路

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

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

需求收集好之后,作为一个有追求的程序员, 会得出如下线框图: 精通React/Vue系列之实现一个全局提示(Message)组件 具体的设计细节可以参考我的上一篇Notification组件设计的文章。

2. 基于react实现一个全局提示(Message)组件

组件的核心部分我们还是采用React Notification的模式。

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

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

import Notification from 'rc-notification'
import './index.less'

const xMessage = (function() {
  let message = null
  /**
     * notice类型弹窗
     * @param {config}  object 提示框配置属性
     *   @param {type} string 提示窗类型
     *   @param {btn}  ReactNode 自定义关闭按钮
     *   @param {className}  string 自定义 CSS class
     *   @param {duration}  number 默认 4.5 秒后自动关闭,配置为 null 则不自动关闭
     *   @param {getContainer}  HTMLNode 配置渲染节点的输出位置
     *   @param {icon}  ReactNode 自定义图标
     *   @param {key}  string 当前提示唯一标志
     *   @param {content}  string|ReactNode 提示标题,必选
     *   @param {onClose}  func 点击默认关闭按钮时触发的回调函数
     *   @param {onClick}  func 点击提示时触发的回调函数
     *   @param {top}  number 消息从顶部弹出时,距离顶部的位置,单位像素
     *   @param {closeIcon}  ReactNode 自定义关闭图标
     */
  const pop = (config) => {
    const {
      type, className,
      duration = 4.5,
      getContainer = () => document.body,
      icon, key, content, onClose, onClick,
      top, closable = true, closeIcon
    } = config
    message.notice({
      content: <div className={classnames('xMessage', className )}>
        <div className={classnames('iconWrap', type)}>
            <Icon type={iconType[type]} />
          </div>
        <div className="xNoticeTit">
          { content }
        </div>
      </div>,
      key, closable, getContainer,
      onClose() {
        onClose && onClose()
      },
      onClick() {
        onClick && onClick()
      },
      closeIcon, duration, style: { top }
    })
  }

  /**
     * 提示提示组件, 全局参数
     * @param {duration} number 默认自动关闭延时,单位秒
     * @param {getContainer} HTMLNode 配置渲染节点的输出位置,默认document.body
     * @param {closeIcon} HTMLNode 自定义关闭图标
  */
  const config = (config) => {}
  const remove = (key) => {}
  const destroy = () => {}

  if(message) {
    return {
      config, pop, remove, destroy
    }
  }
  // 如果为创建实例,则创建默认实例
  Notification.newInstance({}, (notice) => message = notice)

  return {
    config, pop, remove, destroy
  }
})()

export default xMessage

首先我们根据需求把属性罗列出来, 通过分析我们因该对外提供四个接口供开发者使用,分别为:

  • config —— Message全局配置,用来控制全局的偏移量,样式,渲染容器等;
  • pop —— 用来创建全局提示实例的方法,同时可以控制实例的属性
  • remove —— 用来删除指定实例
  • destroy —— 用来销毁全局的Message

首先我们来实现一下config:

const config = (config) => {
  const { duration, getContainer, closeIcon } = config

  Notification.newInstance({
    getContainer: getContainer,
    duration: duration || 4.5,
    closeIcon
  }, (notice) => message = notice)
}

当然我们还可以根据自己的需求去自定义扩展。

pop方法的实现:

const pop = (config) => {
    const {
      type,
      className,
      duration = 4.5,
      getContainer = () => document.body,
      icon,
      key,
      content,
      onClose,
      onClick,
      top,
      closable = true,
      closeIcon
    } = config
    message.notice({
      content: <div className={classnames('xMessage', className )}>
        {
          (icon || ['info', 'success', 'error', 'warning'].indexOf(type) > -1) &&
          <div className={classnames('iconWrap', type)}>
            {
              icon ? icon : <Icon type={iconType[type]} />
            }
          </div>
        }
        <div className="xNoticeTit">
          { content }
        </div>
      </div>,
      key,
      closable,
      getContainer,
      onClose() {
        onClose && onClose()
      },
      onClick() {
        onClick && onClick()
      },
      closeIcon,
      duration,
      style: { top }
    })
  }

该方法主要用来自定义创建全局消息的实例,我们可以这么调用:

xNotification.pop({type: 'success', content: '你的请求被审批通过啦!'})

实际效果如下:

精通React/Vue系列之实现一个全局提示(Message)组件 antd同样的方式会这么调用:

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

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

remove和destroy方法都比较简单,我们直接上代码:

const remove = (key) => {
    message.removeNotice(key)
  }

const destroy = () => {
  message.destroy()
}

由上可以看出他们的实现都是基于message实例自带的API。

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

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

 const iconType = {
    success: 'FaRegCheckCircle',
    warning: 'FaRegMeh',
    info: 'FaRegLightbulb',
    error: 'FaRegTimesCircle'
 }

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

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

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

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

实现效果如下图: 精通React/Vue系列之实现一个全局提示(Message)组件 通过以上步骤, 全局提示(Message)组件就完成了.实现方式和Notification组件有很多相似点,如果不懂的可以在评论区提问,笔者看到后会第一时间解答.

2.3 使用全局提示(Message)组件

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

<Button type="warning" onClick={
        () => {
            message.pop({
                type: 'error',
                content: '趣谈前端学习打卡'
            })
        }
    }>错误信息通知(error)</Button>

配置全局属性:

import { message } from '@alex_xu/xui'

message.config({ duration: 6 })

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

npm i @alex_xu/xui

// 导入xui
import { 
  Button, Skeleton, Empty, Progress,
  Message, Tag, Switch, Drawer, Badge, Alert
} from '@alex_xu/xui'

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

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

npm库截图如下: 精通React/Vue系列之实现一个全局提示(Message)组件

最后

后续笔者将会继续实现

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

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

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

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

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

精通React/Vue系列之实现一个全局提示(Message)组件

更多推荐

点赞
收藏
评论区
推荐文章
秃头王路飞 秃头王路飞
4个月前
webpack5手撸vue2脚手架
webpack5手撸vue相信工作个12年的小伙伴们在面试的时候多多少少怕被问到关于webpack方面的知识,本菜鸟最近闲来无事,就尝试了手撸了下vue2的脚手架,第一次发帖实在是没有经验,望海涵。languageJavaScript"name":"vuecliversion2","version":"1.0.0","desc
技术小男生 技术小男生
4个月前
linux环境jdk环境变量配置
1:编辑系统配置文件vi/etc/profile2:按字母键i进入编辑模式,在最底部添加内容:JAVAHOME/opt/jdk1.8.0152CLASSPATH.:$JAVAHOME/lib/dt.jar:$JAVAHOME/lib/tools.jarPATH$JAVAHOME/bin:$PATH3:生效配置
刚刚好 刚刚好
4个月前
css问题
1、在IOS中图片不显示(给图片加了圆角或者img没有父级)<div<imgsrc""/</divdiv{width:20px;height:20px;borderradius:20px;overflow:h
blmius blmius
1年前
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
晴空闲云 晴空闲云
4个月前
css中box-sizing解放盒子实际宽高计算
我们知道传统的盒子模型,如果增加内边距padding和边框border,那么会撑大整个盒子,造成盒子的宽度不好计算,在实务中特别不方便。boxsizing可以设置盒模型的方式,可以很好的设置固定宽高的盒模型。盒子宽高计算假如我们设置如下盒子:宽度和高度均为200px,那么这会这个盒子实际的宽高就都是200px。但是当我们设置这个盒子的边框和内间距的时候,那
艾木酱 艾木酱
3个月前
快速入门|使用MemFire Cloud构建React Native应用程序
MemFireCloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专
Stella981 Stella981
1年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
1年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
helloworld_28799839 helloworld_28799839
4个月前
常用知识整理
Javascript判断对象是否为空jsObject.keys(myObject).length0经常使用的三元运算我们经常遇到处理表格列状态字段如status的时候可以用到vue
helloworld_34035044 helloworld_34035044
6个月前
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为