资讯专栏INFORMATION COLUMN

Ant design的Notification源码分析

SimpleTriangle / 2657人阅读

摘要:通过将实例传入回调函数。添加再回过头来看回调函数的内容。其中的作用是一次调用传入的各函数,其中方法是移除中相应的节点,是传入的关闭标签后的回调函数。

notification简介

notification就是通知提醒框,在系统四个角显示通知提醒信息。经常用于以下情况:

较为复杂的通知内容。

带有交互的通知,给出用户下一步的行动点。

系统主动推送。

先来看一下notification的API。

API

notification.success(config)

notification.error(config)

notification.info(config)

notification.warning(config)

notification.warn(config)

notification.close(key: String)

notification.destroy()

可以看到,notification的API在antd的组件中可以说是非常另类的,看着是不是有点眼熟,很像经常使用的Console的API,调用起来十分简单。

console.log()

console.error()

console.info()

console.warn()

config的配置也比较简单,主要是标题,内容,关闭时的延时和回调等。详见ANTD的官网。

notification的结构

在分析代码之前,我们先来看下notification的结构,通知组件主要分为三层,由高到低是

NotificationApi => Notification => n*Notice。

NotificationApi

NotificationApi是一个封装的接口,提供统一调用的API,如info(),warn()等。

Notification

Notification是一个Notice容器,就是用来容纳Notice列表的父组件,提供了添加,删除等操作Notice的方法。

Notice

Notice就是我们所看到的通知标签了。

源码分析

先从入口index.js入手,因为这是一个notification的API封装,不是一个组件,所以没有render方法。

//.......省略部分代码........

const api: any = {
  open: notice,//入口
  close(key: string) {
    Object.keys(notificationInstance)
      .forEach(cacheKey => notificationInstance[cacheKey].removeNotice(key));
  },
  config: setNotificationConfig,
  destroy() {
    Object.keys(notificationInstance).forEach(cacheKey => {
      notificationInstance[cacheKey].destroy();
      delete notificationInstance[cacheKey];
    });
  },
};

//.......省略部分代码........

["success", "info", "warning", "error"].forEach((type) => {
  api[type] = (args: ArgsProps) => api.open({
    ...args,
    type,
  });
});

api.warn = api.warning;
export interface NotificationApi {
  success(args: ArgsProps): void;
  error(args: ArgsProps): void;
  info(args: ArgsProps): void;
  warn(args: ArgsProps): void;
  warning(args: ArgsProps): void;
  open(args: ArgsProps): void;
  close(key: string): void;
  config(options: ConfigProps): void;
  destroy(): void;
}
export default api as NotificationApi;

接口比较清晰,可以看出API提供的不同的方法实际是通过一个类似工厂方法的open函数实现的,open函数的具体实现是notice,那么看下这个notice函数。

function notice(args: ArgsProps) {
  const outerPrefixCls = args.prefixCls || "ant-notification";
  const prefixCls = `${outerPrefixCls}-notice`;
  const duration = args.duration === undefined ? defaultDuration : args.duration;

//生成icon组件
  let iconNode: React.ReactNode = null;
  if (args.icon) {
    iconNode = (
      
        {args.icon}
      
    );
  } else if (args.type) {
    const iconType = typeToIcon[args.type];
    iconNode = (
      
    );
  }

  const autoMarginTag = (!args.description && iconNode)
    ? 
    : null;

  getNotificationInstance(outerPrefixCls, args.placement || defaultPlacement, (notification: any) => {
    notification.notice({
      content: (
        
{iconNode}
{autoMarginTag} {args.message}
{args.description}
{args.btn ? {args.btn} : null}
), duration, closable: true, onClose: args.onClose, key: args.key, style: args.style || {}, className: args.className, }); }); }

这段代码主要的部分就是调用了getNotificationInstance函数,看名字应该是得到Notification的实例,命名方式是典型的单例模式,作为列表的容器组件,使用单例模式不仅节省了内存空间,而且单例延迟执行的特性也保证了在没有通知的情况下不会生成notification组件,提升了页面的性能。

function getNotificationInstance(prefixCls: string, placement: NotificationPlacement, callback: (n: any) => void)

查看定义,第一个参数是css前缀,第二个参数是notification的弹出位置,分为topLeft topRight bottomLeft bottomRight,第三个参数是一个回调,回调的参数是notification实例,可以看到,在回调中调用了notification的notice方法,notice方法的参数是一个对象,content看名字应该是通知标签的内容,其他的参数也是调用notification中传入的config参数。
接下来看下getNotificationInstance的实现

function getNotificationInstance(prefixCls: string, placement: NotificationPlacement, callback: (n: any) => void) {
  const cacheKey = `${prefixCls}-${placement}`;
  if (notificationInstance[cacheKey]) {
    callback(notificationInstance[cacheKey]);
    return;
  }

  //---实例化Notification组件
  (Notification as any).newInstance({
    prefixCls,
    className: `${prefixCls}-${placement}`,
    style: getPlacementStyle(placement),
    getContainer: defaultGetContainer,
  }, (notification: any) => {
    notificationInstance[cacheKey] = notification;
    callback(notification);
  });
}

代码很简短,可以看到确实是使用了单例模式,因为存在4个弹出位置,所以将每个位置的notification实例存放在notificationInstance[cacheKey]数组里,cacheKey是css前缀和弹出位置的组合,用以区分每个实例。接下来进入newInstance方法来看下是怎么使用单例模式生成notification实例的。

实例化Notification
Notification.newInstance = function newNotificationInstance(properties, callback) {
  const { getContainer, ...props } = properties || {};
  const div = document.createElement("div");
  if (getContainer) {
    const root = getContainer();
    root.appendChild(div);
  } else {
    document.body.appendChild(div);
  }
  let called = false;
  function ref(notification) {
    if (called) {
      return;
    }
    called = true;
    callback({
      notice(noticeProps) {
        notification.add(noticeProps);
      },
      removeNotice(key) {
        notification.remove(key);
      },
      component: notification,
      destroy() {
        ReactDOM.unmountComponentAtNode(div);
        div.parentNode.removeChild(div);
      },
    });
  }
  ReactDOM.render(, div);
};

主要完成了两件事

通过ReactDOM.render将Notification组件渲染到页面上,可以选择渲染到传入的container或者body中。

通过ref将notification实例传入callback回调函数。

可以看到传入callback的参数对notification又做了一层封装,目的是为了封装destroy函数,其中

+ notice():添加一个notice组件到notification
+ removeNotice():删除指定notice组件。
+ destroy():销毁notification组件。
添加Notice

再回过头来看回调函数的内容。

 getNotificationInstance(outerPrefixCls, args.placement || defaultPlacement, (notification: any) => {
    notification.notice({
      content: (
        
{iconNode}
{autoMarginTag} {args.message}
{args.description}
{args.btn ? {args.btn} : null}
), duration, closable: true, onClose: args.onClose, key: args.key, style: args.style || {}, className: args.className, }); });

调用了notification的notice方法,由前面的代码可知notice其实是调用了Notification组件的add方法,记下来看下add方法是怎样将标签添加进Notification的。

//省略部分代码

 state = {
  notices: [],
};

//省略部分代码

  add = (notice) => {
  const key = notice.key = notice.key || getUuid();
  this.setState(previousState => {
    const notices = previousState.notices;
    if (!notices.filter(v => v.key === key).length) {
      return {
        notices: notices.concat(notice),
      };
    }
  });
}

Notification将要显示的通知列表存在state的notices中,同通过add函数动态添加,key是该notice的唯一标识,通过filter将已存在的标签过滤掉。可以想见,Notification就是将state中的notices通过map渲染出要显示的标签列表,直接进入Notification组件的render方法。

  render() {
  const props = this.props;
  const noticeNodes = this.state.notices.map((notice) => {
    const onClose = createChainedFunction(this.remove.bind(this, notice.key), notice.onClose);
    return (
      {notice.content}
    );
  });
  const className = {
    [props.prefixCls]: 1,
    [props.className]: !!props.className,
  };
  return (
    
{noticeNodes}
); } }

根据state的notices生成Notice组件列表noticeNodes,然后将noticeNodes插入到一个Animate的动画组件中。其中createChainedFunction的作用是一次调用传入的各函数,其中remove方法是移除state中相应的节点,onClose是传入的关闭标签后的回调函数。
看到这里Notification的结构已经比较清晰了,最后再来看下Notice组件的实现。

export default class Notice extends Component {
  static propTypes = {
    duration: PropTypes.number,
    onClose: PropTypes.func,
    children: PropTypes.any,
  };

  static defaultProps = {
    onEnd() {
    },
    onClose() {
    },
    duration: 1.5,
    style: {
      right: "50%",
    },
  };

  componentDidMount() {
    this.startCloseTimer();
  }

  componentWillUnmount() {
    this.clearCloseTimer();
  }

  close = () => {
    this.clearCloseTimer();
    this.props.onClose();
  }

  startCloseTimer = () => {
    if (this.props.duration) {
      this.closeTimer = setTimeout(() => {
        this.close();
      }, this.props.duration * 1000);
    }
  }

  clearCloseTimer = () => {
    if (this.closeTimer) {
      clearTimeout(this.closeTimer);
      this.closeTimer = null;
    }
  }

  render() {
    const props = this.props;
    const componentClass = `${props.prefixCls}-notice`;
    const className = {
      [`${componentClass}`]: 1,
      [`${componentClass}-closable`]: props.closable,
      [props.className]: !!props.className,
    };
    return (
      
{props.children}
{props.closable ? : null }
); } }

这个组件比较简单,主要是实现标签显示一段时间后自动消失,通过setTimeout设置一段时间后调用close方法,也就是上一段代码中实现的移除state中的相应节点以及调用相应的回调函数。

总结

看到这里antd的通知组件的实现已经比较清晰了,代码并没有特别复杂的部分,但是这种使用单例模式动态添加组件的设计十分值得借鉴,在实现类似通知组件或者需要动态添加的组件的时候可以参考这种设计模式,antd的Message组件也采用了同样的设计。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93885.html

相关文章

  • Ant Design源码分析(三):Wave组件

    摘要:返回删除的节点。组件运行逻辑此时,组件的代码与逻辑已经全部分析完了,整个组件的运行逻辑可以通过下方这张图来概括本篇完 Wave组件效果预览        在上一篇文章Button组件的源码分析中遇到了一个Wave组件, Wave组件在Ant design中提供了通用的表单控件点击效果,在自己阅读源码之前,也并没有过更多留心过在这些表单控件的动画效果是如何实现的,甚至可能有时都没注意到这...

    luzhuqun 评论0 收藏0
  • Vue开发总结 及 一些最佳实践 (已更新)

    摘要:基本开发环境创建的项目,作为代码编写工具插件推荐插件配置文章目录项目目录结构介绍框架选择处理请求二次封装项目目录结构简介业务相关静态文件全局组件基础样式布局样式及工具引入请求配置路由全局状态管理工具文件入口文件主要配置文件页面检查配置测试 基本开发环境 vue-cli3 创建的项目,vscode 作为代码编写工具vscode插件推荐:vscode 插件配置 文章目录 项目目录结构介绍...

    NotFound 评论0 收藏0
  • 用React实现一个最最最简单TodoList

    摘要:初学,撸一个熟悉熟悉基本语法,只有最简单最简单的功能。引入这个之后,我们可以直接使用一些简单的组件,比如等,我们可以更加注重业务逻辑的实现。 初学React,撸一个TodoList熟悉熟悉基本语法,只有最简单最简单的功能。 showImg(https://segmentfault.com/img/remote/1460000010376536); 如上图所示,是一个最简单的TodoLi...

    Forest10 评论0 收藏0
  • Ant-Design-组件-——-Form表单(一)

    摘要:擅长网站建设微信公众号开发微信小程序开发小游戏制作企业微信制作建设,专注于前端框架服务端渲染技术交互设计图像绘制数据分析等研究。 Ant Design of React @3.10.9 拉取项目 luwei.web.study-ant-design-pro, 切换至 query 分支,可看到 Form 表单实现效果 实现一个查询表单 showImg(https://segmentfau...

    trilever 评论0 收藏0

发表评论

0条评论

SimpleTriangle

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<