效果圖
很多專案中需要實現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;
}
}
};
複製程式碼