React實現簡單易用Toast

很多專案中需要實現app中常見的提示效果Toast。這個效果看似簡單,實現起來也容易,為了方便,將它封裝成npm元件,方便後續使用。 這裡也是拋磚引玉,可以檢視專案地址一起學習 react-comment-toast
使用方法
import Toast from "react-common-toast"; Toast.info("xxx"); Toast.info("xxx",duration,onClose); 複製程式碼
元件拆分
- 首先是支援多個提示,不同提示定製化也可以不同。取名為Notice。
- Notice外面還有個容器元件,用來裝載Notice並且,暴露一些方法給Toast,起名Notification,是一個單例。
- 最後就是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的關鍵)
// 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等等
// 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方法暴露出去。
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; } } }; 複製程式碼