从antd-design看如何实现一个Message全局提示

3,448 阅读4分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。

Message组件通常用于全局展示操作反馈信息。在页面上我们会有一个操作按钮,用户点击某项操作后,会进行相应的逻辑处理,基于用户操作成功、失败的提示信息来告知用户的操作情况。并且该消息提示一般位于页面顶部,不会影响用户的其他操作。

antd-design中提供了五种样式的消息提示,包括成功、失败、loading等提示,它还可以设置提示时长、更新某条消息等功能。他又提供了那些属性让我们来操作呢?可以见官方文档API,然后我们来简单分析一下它的基础功能,肯定分析的不会那么完善,先看看他所需要的属性吧。

export interface ArgsProps {
  content: React.ReactNode; // 消息提示的内容
  duration?: number; // 显示时间
  type: NoticeType; // 那种消息类型
  onClose?: () => void; // 关闭时回调
  icon?: React.ReactNode; // 消息前面的icon
  key?: string | number; // 消息唯一的key,用来标识、销毁、更新
  style?: React.CSSProperties;
  className?: string;
  onClick?: (e: React.MouseEvent<HTMLDivElement>) => void; // 点击消息
}

antd-design官网例子中,可以知道,我们是调用message.success(config)这种方式来进行消息提示的,所以Message消息提示是导出了一个message的对象,提供了一个属性方法来实现的,当然它还导出了其他的,如getInstance方法,来获取消息的实例。

const api: any = {
  open: notice, // 真正弹出消息的方法
  config: setMessageConfig,
  destroy(messageKey?: React.Key) { },
};
// 在 api 对象上绑定多个方法,内部还是用 open => notice 方法实现的
(['success', 'info', 'warning', 'error', 'loading'] as NoticeType[]).forEach(type =>
  attachTypeApi(api, type),
);
api.warn = api.warning;
api.useMessage = createUseMessage(getRCNotificationInstance, getRCNoticeProps);

interface MessageApi {};
export default api as MessageApi;

我们先定义了一个对象api,给它定义了不同的属性,然后对于类似属性'success', 'info', 'warning', 'error', 'loading'就直接调用工厂函数attachTypeApi来对生成的函数绑定在api对象上。然后进行导出,在外部调用静态方法就可以弹出提示了。

// 往 api 对象上绑对应的方法
export function attachTypeApi(originalApi: MessageApi, type: NoticeType) {
// api['success]...本质上还是调用open方法(notcie方法)
  originalApi[type] = (
    content: JointContent,
    duration?: ConfigDuration,
    onClose?: ConfigOnClose,
  ) => {
    if (isArgsProps(content)) {
      return originalApi.open({ ...content, type });
    }

    // 没传 duration 时,第二项就是 回调函数
    if (typeof duration === 'function') {
      onClose = duration;
      duration = undefined;
    }

    return originalApi.open({ content, duration, type, onClose });
  };
}

message.success(config)为例,调用该方法后,它是一个怎样的流程呢?

  • 从上面看到调用success方法,本质上就是调用notcie方法
// 弹出·message 提示的函数
function notice(args: ArgsProps): MessageType {
  const target = args.key || getKeyThenIncreaseKey(); // 没有传入key,则采用默认的key,从 1 开始 ++
  // 关闭的
  const closePromise = new Promise(resolve => {
    const callback = () => {
      if (typeof args.onClose === 'function') {
        args.onClose();
      }
      return resolve(true);
    };

    getRCNotificationInstance(args, ({ prefixCls, iconPrefixCls, instance }) => {
      // rc-notification 返回的·实例 instance.notice
      instance.notice(
        getRCNoticeProps({ ...args, key: target, onClose: callback }, prefixCls, iconPrefixCls),
      );
    });
  });
  const result: any = () => {
    // 移除
    if (messageInstance) {
      messageInstance.removeNotice(target);
    }
  };
  result.then = (filled: ThenableArgument, rejected: ThenableArgument) =>
    closePromise.then(filled, rejected);
  result.promise = closePromise;
  return result;
}

getRCNotificationInstance方法中主要就是根据传入的props来调用RCNotification.newInstance()方法,RCNotification其实是用的rc-notification模块,大家感兴趣可以搜一下看看他的使用方法。RCNotification.newInstance(instanceConfig,(instance: any) => {})方法会在回调函数中传入消息实例instance,然后将该实例传入我们定义的回调函数中,接收到实例后调用instance.notice()方法来进行提示,它需要的参数是经过getRCNoticeProps()方法处理的,该方法主要就是生成我们的props来传入到instance.notice()方法中。

function getRCNotificationInstance(
  args: ArgsProps,
  callback: (info: {
    prefixCls: string;
    rootPrefixCls: string;
    iconPrefixCls: string;
    instance: RCNotificationInstance;
  }) => void,
) {
  // ...

  if (messageInstance) {
    callback({ prefixCls, rootPrefixCls, iconPrefixCls, instance: messageInstance });
    return;
  }

  // 初次实例化
  const instanceConfig = {
    prefixCls,
    transitionName: hasTransitionName ? transitionName : `${rootPrefixCls}-${transitionName}`,
    style: { top: defaultTop }, // 覆盖原来的样式
    getContainer: getContainer || getContextPopupContainer,
    maxCount,
  };

  RCNotification.newInstance(instanceConfig, (instance: any) => {
    if (messageInstance) {
      callback({ prefixCls, rootPrefixCls, iconPrefixCls, instance: messageInstance });
      return;
    }
    messageInstance = instance;
    callback({ prefixCls, rootPrefixCls, iconPrefixCls, instance });
  });
}

那我们如何关闭一个消息呢,除了默认关闭外,我们还可以调用message.destroy(key?:string)方法来关闭它。

const api: any = {
  // ...
  destroy(messageKey?: React.Key) {
    // messageInstance 是messgae组件的实例,实际上是rc-notification模块返回的instance实例
    // removeNotice、destroy都是由instance实例提供的
    if (messageInstance) {
      // 有key就只移除他自己,掉自身的 方法removeNotice
      if (messageKey) {
        const { removeNotice } = messageInstance;
        removeNotice(messageKey);
      } else {
        // 全局销毁
        const { destroy } = messageInstance;
        destroy();
        messageInstance = null;
      }
    }
  },
};

至此,一个基础的消息提示就封装好了,主要还是依赖了rc-notification模块,大家感兴趣可以看看。

相关文档: