Ant design的Notification源码剖析

notification简介

《Ant design的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的构造,关照组件重要分为三层,由高到低是

<font color=”blue”>NotificationApi => Notification => n*Notice。</font>

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 = (
      <span className={`${prefixCls}-icon`}>
        {args.icon}
      </span>
    );
  } else if (args.type) {
    const iconType = 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;

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

这段代码重要的部份就是挪用了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(<Notification {...props} ref={ref} />, 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: (
        <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,
    });
  });

挪用了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
      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>
  );
}
}

依据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 (
      <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>
    );
  }
}

这个组件比较简朴,主如果完成标签显现一段时间后自动消逝,经由过程setTimeout设置一段时间后挪用close要领,也就是上一段代码中完成的移除state中的响应节点以及挪用响应的回调函数。

总结

看到这里antd的关照组件的完成已比较清楚了,代码并没有迥殊庞杂的部份,然则这类运用单例形式动态增加组件的设想非常值得自创,在完成相似关照组件或许须要动态增加的组件的时刻能够参考这类设想形式,antd的Message组件也采用了一样的设想。

    原文作者:水星的卫星
    原文地址: https://segmentfault.com/a/1190000014118419
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞