React實現簡單易用Toast

效果圖

不少項目中須要實現app中常見的提示效果Toast。這個效果看似簡單,實現起來也容易,爲了方便,將它封裝成npm組件,方便後續使用。 這裏也是拋磚引玉,能夠查看項目地址一塊兒學習react-comment-toastcss

使用方法

import Toast from "react-common-toast";
Toast.info("xxx");
Toast.info("xxx",duration,onClose);
複製代碼

組件拆分

  1. 首先是支持多個提示,不一樣提示定製化也能夠不一樣。取名爲Notice。
  2. Notice外面還有個容器組件,用來裝載Notice而且,暴露一些方法給Toast,起名Notification,是一個單例。
  3. 最後就是Toast組件,負責直接生成不一樣的Notice,或者銷燬Notification。可是其實Toast只是個對象,而不是真正意義的組件。

Notification

Notification是個容器,裏面有一個notice數組。 而後render的時候,循環notices生成一段DOM節點,放到本身的div中。 同時,其還提供一個向notices中添加notice的方法(add)和根據key,在notices中刪除notice的方法(remove)。 最後關鍵的地方,定義一個reRwrite方法,該方法接受一些參數,動態的向DOM中插入一個div,而後再向這個div中插入Notification,最後返回一個含有幾個操做這個Notification的方法的對象。(這就是動態實現插入DOM的關鍵)react

// Notification是Notice父組件,容器
// 是動態插入和刪除DOM節點的核心
// 同時也向上暴露給Toast重寫改變本身的方法
import React from "react";
import ReactDOM from "react-dom";
import Notice from "./Notice";
import "./toast.css";

// 統計notice總數 防止重複
let noticeNumber = 0;
// 生成惟一的id
const getUuid = () => {
  return "notification-" + new Date().getTime() + "-" + noticeNumber++;
};
export default class Notification extends React.Component {
 

  constructor(props) {
    super(props);
    this.state = {
      notices: [], // 存儲當前有的notices
      hasMask: true // 是否顯示蒙版
    };
  }
  add(notice) {
    // 添加notice
    // 創造一個不重複的key
    const { notices } = this.state;
    const key = notice.key ? notice.key : (notice.key = getUuid());
    const mask = notice.mask ? notice.mask : false;
    const temp = notices.filter(item => item.key === key).length;

    if (!temp) {
      // 不存在重複的 添加
      notices.push(notice);
      this.setState({
        notices: notices,
        hasMask: mask
      });
    }
  }
  remove(key) {
    // 根據key刪除對應
    this.setState(previousState => {
      return {
        notices: previousState.notices.filter(notice => notice.key !== key)
      };
    });
  }
  getNoticeDOM() {
    const _this = this;
    const { notices } = this.state;
    let result = [];

    notices.map(notice => {
      // 每一個Notice onClose的時候 刪除掉notices中對應key的notice
      const closeCallback = () => {
        _this.remove(notice.key);
        // 若是有用戶傳入的onClose 執行
        if (notice.onClose) notice.onClose();
      };

      result.push(
        <Notice key={notice.key} {...notice} onClose={closeCallback} />
      );
    });

    return result;
  }
  getMaskDOM() {
    const { notices, hasMask } = this.state;
    // notices爲空的時候 不顯示蒙版
    // 始終只有一個蒙版
    if (notices.length > 0 && hasMask == true)
      return <div className="tips-mask" />;
  }
  render() {
    const noticesDOM = this.getNoticeDOM();
    //暫時沒有配置蒙版
    const maskDOM = this.getMaskDOM();
    return (
      <div>
        {/*{maskDOM}*/}
        {noticesDOM}
      </div>
    );
  }
}

 // Notification增長一個重寫方法
  // 該方法方便Notification組件動態添加到頁面中和重寫
Notification.reWrite = properties => {
  const { ...props } = properties || {};

  let div = document.createElement("div");
  document.body.appendChild(div);

  const notification = ReactDOM.render(<Notification {...props} />, div);

  return {
    notice(noticeProps) {
      notification.add(noticeProps);
    },
    removeNotice(key) {
      notification.remove(key);
    },
    destroy() {
      ReactDOM.unmountComponentAtNode(div);
      document.body.removeChild(div);
    },
    component: notification
  };
};
複製代碼

Notice

主要是負責接受一些參數,duration,icon,content等等git

// Notice是Toast最底層組件
// 每一個toast的小框框其實都是一個Notice
// Notice核心就是組件初始化的時候 生成一個定時器
// 根據輸入的時間 加載一個動畫 而後執行輸入的回調
// Notice的顯示和隱藏收到父組件Notification的絕對控制
import React from "react";
import classNames from "classnames";
import { PropTypes } from "prop-types";

export default class Notice extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      shouldClose: false // 是否開啓關閉動畫
    };
  }
  componentDidMount() {
    if (this.props.duration > 0) {
      this.closeTimer = setTimeout(() => {
        this.close();
      }, this.props.duration - 300); // 減掉消失動畫300毫秒
    }
  }
  componentWillUnmount() {
    // 當有意外關閉的時候 清掉定時器
    this.clearCloseTimer();
  }
  clearCloseTimer() {
    if (this.closeTimer) {
      clearTimeout(this.closeTimer);
      this.closeTimer = null;
    }
  }
  close() {
    // 關閉的時候 應該先清掉倒數定時器
    // 而後開啓過場動畫
    // 等待動畫結束 執行回調
    this.clearCloseTimer();
    const _this = this;
    _this.setState({ shouldClose: true });
    this.timer = setTimeout(() => {
      if (this.props.onClose) {
        this.props.onClose();
      }
      clearTimeout(_this.timer);
    }, 300);
  }
  render() {
    const { shouldClose } = this.state;
    return <div className={classNames({ leave: shouldClose })}>
        {this.props.content}
      </div>
  }
}

Notice.propTypes = {
  duration: PropTypes.number, // Notice顯示時間
  content: PropTypes.any, // Notice顯示的內容
  onClose: PropTypes.func // 顯示結束回調
};

Notice.defaultProps = {
  duration: 3000
};
複製代碼

Toast

Toast首先就是要利用Notification.reWrite初始化一個newNotification,而且保持這個Notification爲單例。 而後封裝一個notice方法,動態的改變這個newNotification。 最後封裝幾個經常使用notice方法暴露出去。github

import React from "react";
import classNames from "classnames";
import Notification from "./Notification";
// Toast組件比較特殊
// 由於<Toast />不會被直接渲染在DOM中
// 而是動態插入頁面中
// Toast組件核心就是經過Notification暴露的重寫方法 動態改變Notification
let newNotification;

// 得到一個Notification
const getNewNotification = () => {
  // 單例 保持頁面始終只有一個Notification
  if (!newNotification) {
    newNotification = Notification.reWrite();
  }

  return newNotification;
};
// notice方法實際上就是集合參數 完成對Notification的改變
const notice = (content, type, duration = 3000, onClose, mask = true) => {
  if (!content) return;

//   content = content.toString();

  let notificationInstance = getNewNotification();

  notificationInstance.notice({
    duration,
    mask: mask,
    content: (
      <div className={classNames(["tips-notice-box"])}>
        <div
          className={classNames([
            "tips-notice-content",
            { info: type === "info" },
            { success: type === "success" },
            { warning: type === "warning" },
            { error: type === "error" }
          ])}
        >
          {content}
        </div>
      </div>
    ),
    onClose: () => {
      if (onClose) onClose();
    }
  });
};

export default {
  show(content, duration, icon, mask, onClose) {
    return notice(content, undefined, icon, duration, onClose, mask);
  },
  info(content, duration, icon, mask, onClose) {
    return notice(content, "info", icon, duration, onClose, mask);
  },
  success(content, duration, icon, mask, onClose) {
    return notice(content, "success", icon, duration, onClose, mask);
  },
  warning(content, duration, icon, mask, onClose) {
    return notice(content, "warning", icon, duration, onClose, mask);
  },
  error(content, duration, icon, mask, onClose) {
    return notice(content, "error", icon, duration, onClose, mask);
  },
  hide() {
    if (newNotification) {
      newNotification.destroy();
      newNotification = null;
    }
  }
};
複製代碼
相關文章
相關標籤/搜索