Notification profile

  • Relatively complex notification content.
  • An interactive notification that gives the user the next action point.
  • The system actively pushes.

Let’s take a look at the API for Notification.

API

  • notification.success(config)
  • notification.error(config)
  • notification.info(config)
  • notification.warning(config)
  • notification.warn(config)
  • notification.close(key: String)
  • notification.destroy()

As you can see, the Notification API is very special in antD components. If it looks familiar, it is very similar to the Console API that is often used. It is very simple to call.

  • console.log()
  • console.error()
  • console.info()
  • console.warn()

The config configuration is also relatively simple, mainly including title, content, closing delay and callback. Please refer to ANTD’s official website for details.

The structure of the notification

Before analyzing the code, let’s take a look at the structure of Notification. Notification components are mainly divided into three layers, from outside to inside

Notification => Notification => N *Notice.

NotificationApi

NotificationApi is an encapsulated interface that provides a unified call API, such as INFO (), WARN (), and so on.

Notification

Notification is a Notice container, which is the parent component of the Notice list. It provides methods for adding and deleting notices.

Notice

Notice is the Notice label we see.

Source code analysis

Start with the entry index.js, because this is an API wrapper for Notification, not a component, so there is no render method.

/ /... Omit some code........

const api: any = {
  open: notice,/ / the entry
  close(key: string) {
    Object.keys(notificationInstance)
      .forEach(cacheKey= > notificationInstance[cacheKey].removeNotice(key));
  },
  config: setNotificationConfig,
  destroy() {
    Object.keys(notificationInstance).forEach(cacheKey= > {
      notificationInstance[cacheKey].destroy();
      deletenotificationInstance[cacheKey]; }); }};/ /... Omit some code........

// Different types are implemented by passing in the open argument
['success'.'info'.'warning'.'error'].forEach((type) = > {
  api[type] = (args: ArgsProps) = >api.open({ ... args,type}); }); api.warn = api.warning;// Encapsulate the interface
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;
Copy the code

The different methods provided by the API are actually implemented by an open function similar to the factory method. The notice function is implemented by the open function, so let’s look at the notice function.

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

// Generate the icon component
  let iconNode: React.ReactNode = null;
  if (args.icon) {
    iconNode = (
      <span className={` ${prefixCls}-icon`} >
        {args.icon}
      </span>
    );
  } else if (args.type) {
    consticonType = typeToIcon[args.type]; iconNode = ( <Icon className={`${prefixCls}-icon ${prefixCls}-icon-${args.type}`} type={iconType} /> ); } const autoMarginTag = (! args.description && iconNode) ? <span className={`${prefixCls}-message-single-line-auto-margin`} /> : null; / / get the Notification instance getNotificationInstance (outerPrefixCls, args. The placement | | defaultPlacement, (Notification: any) => { notification.notice({ content: ( <div className={iconNode ? `${prefixCls}-with-icon` : ''}> {iconNode} <div className={`${prefixCls}-message`}> {autoMarginTag} {args.message} </div> <div className={`${prefixCls}-description`}>{args.description}</div> {args.btn ? <span className={`${prefixCls}-btn`}>{args.btn}</span> : null} </div> ), duration, closable: true, onClose: args.onClose, key: args.key, style: args.style || {}, className: args.className, }); }); }Copy the code

The main part of this code is to call getNotificationInstance. The name should be the instance of the Notification, and the naming method is typical singleton mode. As the container component of the list, using singleton mode not only saves memory space, The singleton deferred execution feature also ensures that no Notification component is generated without notification, improving page performance.

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

The first parameter is the CSS prefix, the second parameter is the popup position of the Notification, divided into topLeft topRight bottomLeft bottomRight, and the third parameter is a callback. The callback is an instance of the Notification, as you can see, The notice method of notification is called in the callback. The parameter of notice method is an object. The content of notice method should be the content of the notification label. Let’s look at the implementation of getNotificationInstance

function getNotificationInstance(prefixCls: string, placement: NotificationPlacement, callback: (n: any) => void) { const cacheKey = `${prefixCls}-${placement}`; if (notificationInstance[cacheKey]) { callback(notificationInstance[cacheKey]); return; } //-- instantiate the Notification component (Notification as any). NewInstance ({prefixCls, className: '${prefixCls}-${placement}', style: getPlacementStyle(placement), getContainer: defaultGetContainer, }, (notification: any) => { notificationInstance[cacheKey] = notification; callback(notification); }); }Copy the code

The code is very short. You can see that the singleton pattern is used. Since there are four pop-up locations, store an instance of notification for each location in an array notificationInstance[cacheKey]. To distinguish each instance. Next, enter the newInstance method to see how the singleton pattern is used to generate a Notification instance.

Instantiate the 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(<Notification {... props} ref={ref} />, div); };Copy the code

Two main things have been accomplished

  • Render the Notification component to the page via reactdom.render, optionally into the container or body passed in.
  • Pass the Notification instance to the callback callback function via ref. As you can see, the argument passed to the callback wraps another layer around the Notification to encapsulate the destroy function, where
    • Notice (): Adds a NOTICE component to the Notification
    • RemoveNotice (): Removes the specified Notice component.
    • Destroy (): Destroys the Notification component.

Add the Notice

Let’s go back to the callback function.

 getNotificationInstance(outerPrefixCls, args.placement || defaultPlacement, (notification: any) => {
    notification.notice({
      content: (
        <div className={iconNode ? `${prefixCls}-with-icon` : ''}>
          {iconNode}
          <div className={`${prefixCls}-message`}>
            {autoMarginTag}
            {args.message}
          </div>
          <div className={`${prefixCls}-description`}>{args.description}</div>
          {args.btn ? <span className={`${prefixCls}-btn`}>{args.btn}</span> : null}
        </div>
      ),
      duration,
      closable: true,
      onClose: args.onClose,
      key: args.key,
      style: args.style || {},
      className: args.className,
    });
  });
Copy the code

Notice calls the notice method of the Notification component. Notice calls the add method of the Notification component. Note how the add method adds the tag to the Notification.

// Omit some code

 state = {
  notices: [],
};

// Omit some code

  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), }; }}); }Copy the code

Notice The notice list to be displayed exists in the state notice and is dynamically added through the add function. The key is the unique identifier of the notice, and the existing labels are filtered out through filter. As we can imagine, Notification is to render the list of labels to be displayed through the map and directly enter the render method of the Notification component.

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 prefixCls={props.prefixCls} {... notice} onClose={onClose} > {notice.content} </Notice>); }); const className = { [props.prefixCls]: 1, [props.className]: !! props.className, }; return ( <div className={classnames(className)} style={props.style}> <Animate transitionName={this.getTransitionName()}>{noticeNodes}</Animate> </div> ); }}Copy the code

According to the state, the Notice component list noticeNodes is generated, and then the noticeNodes is inserted into an Animate component. Where createChainedFunction is used to call each function passed in one time, where remove method is to remove the corresponding node in the state, and onClose is the callback function passed in to close the tag. Now that the structure of Notification is clear, let’s finally look at the implementation of the Notice component.

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; z const componentClass = `${props.prefixCls}-notice`; const className = { [`${componentClass}`]: 1, [`${componentClass}-closable`]: props.closable, [props.className]: !! props.className, }; return ( <div className={classNames(className)} style={props.style} onMouseEnter={this.clearCloseTimer} onMouseLeave={this.startCloseTimer} > <div className={`${componentClass}-content`}>{props.children}</div> {props.closable ? <a tabIndex="0" onClick={this.close} className={`${componentClass}-close`}> <span className={`${componentClass}-close-x`}></span> </a> : null } </div> ); }}Copy the code

This component is relatively simple, mainly to achieve the label display automatically disappear after a period of time, through setTimeout set after a period of time to call the close method, that is, to remove the corresponding node in the state and call the corresponding callback function implemented in the previous code.

conclusion

The implementation of antD notification component is quite clear. There is no particularly complex part of the code, but the design of dynamically adding components using singleton pattern is worth learning from. When implementing similar notification components or components that need to be added dynamically, you can refer to this design pattern. Antd’s Message component uses the same design.