需求:
实现一个在页面右下角显示出来的内容通知框,通过react函数调用的方式来完成这个功能.例如我在页面组件只需要调用$notify.send("该付款了!")这个函数,那么通知框就会弹出并显示内容 "该付款了!".
界面效果:
具体实现过程:
1.创建一个文件夹Notification,里面盛放所有通知组件的内容,里面分别创建三个文件 index.js call.js style.scss
index.js是Notification组件的具体内容,style.scss是该组件的样式.为了让组件进出场有动画效果,需要安装一个动画库.
npm install react-transition-group --save
index.js
import React,{Component,Fragment} from "react";
import "./style.scss";
import { CSSTransition } from 'react-transition-group';
export default class Notification extends Component{
constructor(props){
super(props);
this.state = {
content:"",
verticle_btn:20,
show:false //这里的show必须要设置为false,如果设置为true的话,进场没有动画.
}
}
setContent = (data)=>{
let obj = {
content:data.content,
verticle_btn:data.verticle_btn,
show:true //想要有进场动画,show需要有一个由false变成true的过程
}
this.setState(obj);
}
fade = (flag)=>{
this.setState({
show:flag
})
}
updateHeight = (height)=>{
this.setState({
verticle_btn:height
})
}
componentDidMount(){
this.loadTimeout((this.props.index+1)*3000);
}
loadTimeout = (n = 3000)=>{
this.timer = setTimeout(()=>{
clearTimeout(this.timer);
this.fade(false);
},n)
}
componentWillUnmount(){
if(this.timer){
clearTimeout(this.timer);
}
}
enter = ()=>{
this.closeTimeout(this);
}
out = ()=>{
this.openTimeout(this);
}
render(){
const { content,verticle_btn,show } = this.state;
return (
<div className="notification_box">
<CSSTransition in={show} timeout={250} classNames='fade' unmountOnExit
onExited={() => {
this.animationEnd(this);
}}
>
<div className="notification" style={{bottom:`${verticle_btn}px`}} onMouseOver={this.enter} onMouseOut={this.out}>
{content?content:"没有通知内容"}
<span className="close" onClick={this.close}>关闭</span>
</div>
</CSSTransition>
</div>
)
}
}
style.scss
.notification_box{
.notification{
width: 850px;
height: 200px;
background-color: rgba(0,0,0,0.6);
border-radius: 10px;
text-align: center;
line-height: 200px;
color:#fff;
font-size: 14px;
position: fixed;
right:20px;
.close{
position: absolute;
top: -34px;
right: 20px;
color: #eee;
font-size: 14px;
height: 118px;
}
}
.fade-enter, .fade-appear {
opacity: 0;
}
.fade-enter-active, .fade-appear-active {
opacity: 1;
transition: opacity 0.25s ease-in;
}
.fade-enter-done {
opacity: 1;
}
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
right:200px;
transition: all .25s ease-in;
}
.fade-exit-done {
opacity: 0;
}
}
call.js
import Notification from "./index";
import ReactDOM from "react-dom";
import React from "react";
export default {
send(msg = "没有内容"){ //发送通知
const notify = createNotification();
notify.setContent(getData(msg,notify));
notify.close = ()=>{
notify.fade(false);
}
notify.animationEnd = (notify)=>{
destory(notify);
countVerticleHeight();
}
notify.closeTimeout = closeTimeout;
notify.openTimeout = openTimeout;
}
}
/**
* 鼠标划入通知框
*/
const closeTimeout = ()=>{
notifications.forEach((v)=>{
clearTimeout(v.instance.timer);
})
}
/**
* 鼠标移出通知框
*/
const openTimeout = ()=>{
notifications.forEach((v,i)=>{
v.instance.loadTimeout((i+1)*3000);
})
}
/**
* 计算出每个通知框距离底部的高度和内容
*/
const getData = (content,notify)=>{
const index = notifications.findIndex((v)=>{
return v.instance == notify;
})
const padding = 30;
const verticle_btn = (index+1)*padding +(index+1)*200;
return {
content,
verticle_btn
}
}
//通知的集合
let notifications = [];
const createNotification = ()=>{
let DIV = document.createElement("DIV");
document.body.appendChild(DIV);
const notify = ReactDOM.render(<Notification notifications={notifications} index={notifications.length}/>,DIV);
notifications.push({
dom:DIV,
instance:notify
});
return notify;
}
//销毁组件
const destory = (notify)=>{
let index = notifications.findIndex((v)=>{
return v.instance == notify;
})
if(index == -1){
return false;
}
const dom = notifications[index].dom;
ReactDOM.unmountComponentAtNode(dom);
document.body.removeChild(dom);
notifications.splice(index,1);
}
/**
* 重新计算每个组件距离底部的高度
*/
const countVerticleHeight = ()=>{
const padding = 30;
notifications.forEach((v,index)=>{
const verticle_btn = (index+1)*padding +(index+1)*200;
v.instance.updateHeight(verticle_btn);
})
}
分析:
call.js是暴露给外部调用的核心文件.,其中有一个核心函数send就是给其他页面直接调用的.一旦执行send函数,会将通知内容作为参数传递进来。紧接着会执行createNotification()函数,createNotification函数的作用是创建一个Notification组件,并挂载到dom树上,然后将创建的该组件的实例缓存在notifications这个数组中,并且将实例作为该函数的返回值.获取到该Notification组件实例后,调用组件上的方法setContent可以触发setState致使render方法执行渲染页面.setContent这个函数除了要传递通知框的内容,而且还包括距离底部的高度.这样就可以显示新增多个通知框时,每个通知框按顺序从上到下排列.最后给实例增加一些销毁和动画相关的方法.在每个实例初始化完毕后会在componentDidMount方法中执行一个定时器,3s后自动销毁.
当鼠标滑入通知框时,我们希望所有通知框保持静止,通过触发closeTimeout方法将每个通知框的实例中的定时器清除.而当鼠标移出时,又通过openTimeout给每个实例重新添加上定时器,为了让通知框消失的错落有致, 我们可以将定时器的定时时间作为参数传递进去.另外每当有某个实例被销毁后,我们要对notifications中存储的所有通知框实例进行重新计算它距离底部的距离,这样才会有上图中当底部的通知框消失时上层的通知框自然下落的效果.
核心原理:
在页面上调用send方法时,会创建一个Notification组件,并把它挂载到document.body下,然后给组件添加一些方法,在组件存活3秒后会自动触发销毁的方法,并从document.body下移除所有相关内容.整个过程就是每调用一次send,就会创建一个react组件并挂载在dom下展现一个通知框,调用2次就会创建两个,过了几秒钟又会自动销毁.
2.调用
在页面组件当中只需要将call.js引入,然后直接执行$notify.send方法就可以生成一个通知框,通过这种函数式调用的方式可以使代码更加精简和优雅.
import React, { Component } from 'react';
import "./style.scss";
import $notify from "../../components/Notification/call";
class Homepage extends Component {
//发送通知
sendNotification = ()=>{
$notify.send("该付款了!");
}
render() {
return (
<div className="home">
hello world
<p><button className="btn" onClick={this.sendNotification}>点我啊</button></p>
</div>
)
}
}
export default Homepage;