1. Application scenarios

Global notification Indicates a notification

  • Relatively complex notification content
  • An interactive notification that gives the user the next action point
  • System active push

2. Notification API

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

3. Source code analysis

3.1 Design Roadmap

Use the method in the Notification Class to generate the Notification Component for the corresponding location, and use the Notification Component to render the Notice Components

  • notificationInstance

To cache the created Instance of the Notification Component and then remove the cached instance on destruction

const notificationInstance = {}; 

// Destroy the instance
destroy() {
  Object.keys(notificationInstance).forEach(cacheKey= > {
    notificationInstance[cacheKey].destroy()
    delete notificationInstance[cacheKey]
  })
}
// Cache instance singleton pattern prevents instance duplication
getNotificationInstance(
  {
      //. Omit some code........ }, callback: (n:any) = >void.) {
  const cacheKey = `${prefixCls}-${placement}`;
  if (notificationInstance[cacheKey]) {
    callback(notificationInstance[cacheKey]);
    return;
  }
  (Notification as any).newInstance(
    {
      / /... Omit some code........
    },
    (notification: any) = > {
      // Cache the instancenotificationInstance[cacheKey] = notification; callback(notification); }); }Copy the code

  • noticeInstance

    class Notification extends Component<NotificationProps.NotificationState> {
      state: NotificationState = {
        notices: [].// notice instance cache
      }
      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), }; }});// Destroy the instance
      remove = (key) = > {
        this.setState(previousState= > {
          return {
            notices: previousState.notices.filter(notice= >notice.key ! == key), }; }); }render() {
        const { notices } = this.state;
        const noticeNodes = notices.map((notice, index) = > {
          / /... Omit some code........
          return (
            <Notice
              prefixCls={prefixCls}
              closeIcon={closeIcon}
              {. notice}
              key={key}
              update={update}
              onClose={onClose}
              onClick={notice.onClick}
            >
              {notice.content}
            </Notice>
          );
        });
        return (
          <>
            {noticeNodes}
          </>); }}Copy the code

    3.2 Implementation Details

    Before analyzing the code, let’s take a look at the structure of the Notification component, which is divided into three layers

  • The NotificationApi call stack is shown

The package of notificationApi component provides API interfaces such as notification. Success notification. Error notification. Open Notification

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();
      deletenotificationInstance[cacheKey]; }); }}; ['success'.'info'.'warning'.'error'].forEach(type= > {
  api[type] = (args: ArgsProps) = >api.open({ ... args,type}); });Copy the code

Notice the NotificationApi exposes several interfaces, most of which are implemented through a factory function like open, which in turn refers to notice. Notice

function notice(args: ArgsProps) {
  const outerPrefixCls = args.prefixCls || 'ant-notification';
  / /... Omit some code........
  const { placement, top, bottom, getContainer } = args;
  // Generate instance methods to invoke the interface exposed by the instance
  getNotificationInstance(
    {
      / /... Omit some code........
    },
    (notification: any) = > {
      notification.notice({
        content: (), duration, ...... }); }); }Copy the code

GetNotificationInstance naming is a typical singleton pattern. Using singleton pattern not only saves memory space, but also ensures that the singleton delay will not generate notification components without notification, ensuring performance

 // The singleton pattern prevents instances from being generated repeatedly
function getNotificationInstance({
    prefixCls,
    placement,
    getContainer,
  }, callback: (n: any) = >void) {
  const cacheKey = `${prefixCls}-${placement}`;
  if (notificationInstance[cacheKey]) {
    callback(notificationInstance[cacheKey]);
    return;
  }
  // Instantiate the Notification component
  (Notification as any).newInstance(
    {
     / /... The configuration information........ is omitted
    },
    (notification: any) = > {
      // Cache the instancenotificationInstance[cacheKey] = notification; callback(notification); }); }Copy the code

The first parameter to getNotificationInstance is configuration information, such as CSS prefix, pop-up location, notification container, and so on. A cacheKey is generated based on a free combination of the first two conditions, i.e., a different pop-up location/CSS prefix generates multiple Instances of Notification. Otherwise, a notificationInstance is cached no matter how many times it is called. This is done by calling notification.destroy

  • Notification

    Antd relies heavily on the React-Component library, and notificationApi relies on RC-Notification, which exposes notification instances and instance methods. 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.

    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;
      // How to expose the instance
      function ref(notification: Notification) {
        if (called) {
          return;
        }
        called = true;
        callback({
          notice(noticeProps) {
            notification.add(noticeProps);
          },
          removeNotice(key) {
            notification.remove(key);
          },
          component: notification,
          destroy() {
            // Destroy the Notification instance containerReactDOM.unmountComponentAtNode(div); div.parentNode.removeChild(div); }}); }// Reactdom. render does not support instance generation in antd 3.x
      // The 2.x version solution I also output in Part 4
      ReactDOM.render(<Notification {. props} ref={ref} />, div);
    };
    Copy the code

    Notification component is mainly used to maintain notice generation, notice removal, Notification instance rendering wrapper, Noticelist rendering, etc

    class Notification extends Component<NotificationProps.NotificationState> {
      // Maintain the preceding command
      state: NotificationState = {
        notices: [],}; add =(notice: NoticeContent) = > {
       // notificationApi exposes the key. The key is used to distinguish the uniqueness of the notice generated if the unique key is not generated every time
        notice.key = notice.key || getUuid();
        const { key } = notice;
        const { maxCount } = this.props;
        // maxCount is a limit on the number of instances generated for notice.
        // maxCount is not used internally or exposed externally
        this.setState(previousState= > {
          const { notices } = previousState;
          const noticeIndex = notices.map(v= > v.key).indexOf(key);
          const updatedNotices = notices.concat();
          if(noticeIndex ! = = -1) {
            updatedNotices.splice(noticeIndex, 1, notice);
          } else {
            if (maxCount && notices.length >= maxCount) {
              notice.updateKey = updatedNotices[0].updateKey || updatedNotices[0].key;
              updatedNotices.shift();
            }
            updatedNotices.push(notice);
          }
          return {
            notices: updatedNotices,
          };
        });
      };
      / / remove the notice
      remove = (key: React.Key) = > {
        this.setState(previousState= > ({
          notices: previousState.notices.filter(notice= >notice.key ! == key), })); };render() {
        const { notices } = this.state;
        const { prefixCls, className, closeIcon, style } = this.props;
        const noticeNodes = notices.map((notice, index) = > {
          / /... Omit a long string of code........
          const onClose = createChainedFunction(
            this.remove.bind(this, notice.key),
            notice.onClose,
          ) as any;
          return (
            <Notice
              prefixCls={prefixCls}
              closeIcon={closeIcon}
              {. notice}
              key={key}
              update={update}
              onClose={onClose}
              onClick={notice.onClick}
            >
              {notice.content}
            </Notice>
          );
        });
        return (
          <>
            {noticeNodes}
          </>); }}Copy the code

    createChainedFunction

    • The ability to delete the cached value of the current Notification
    • Executes the closed callback function passed in from the outside
     // rc-utils calls functions in a sequential chain to execute
     export default function createChainedFunction() {
        const args = [].slice.call(arguments.0);
        if (args.length === 1) {
          return args[0];
        }
    
        return function chainedFunction() {
          for (let i = 0; i < args.length; i++) {
            if (args[i] && args[i].apply) {
              args[i].apply(this.arguments); }}}; }Copy the code
  • Notice

    export default class Notice extends Component<NoticeProps> {
      componentDidUpdate(prevProps: NoticeProps) {
        if (this.props.duration ! == prevProps.duration ||this.props.update) {
          this.restartCloseTimer();
        }
      }
      close = (e? : React.MouseEvent
             ) = > {
        if (e) {
          e.stopPropagation();
        }
        this.clearCloseTimer();
        this.props.onClose();
      };
    
      startCloseTimer = () = > {
        if (this.props.duration) {
          this.closeTimer = window.setTimeout(() = > {
            this.close();
          }, this.props.duration * 1000); }}; clearCloseTimer =() = > {
        if (this.closeTimer) {
          clearTimeout(this.closeTimer);
          this.closeTimer = null; }};restartCloseTimer() {
        this.clearCloseTimer();
        this.startCloseTimer();
      }
    
      render() {
        return (
          <></>
        );
      }
    Copy the code

4. Reference materials

  • Github.com/ant-design/…
  • Github.com/react-compo…