最近一段时间开始研究react,对于一个小白来说一个新的框架要学很多东西,不过还好,基本mvvm框架大体都差不多,只是使用上语法有所区别(当然不讨论底层逻辑),但是还是遇到了不少值得自己总结的东西,这次主要说下toast自定义组件。ps:完整代码在最后,可以直接跳过过程查看代码~
在vue中我们定义toast可以定义一个组件,然后写一个install方法转换成插件模式,就可以直接引用了,但是react貌似没有这种方法呢,但是万变不离其宗,它还是一个组件。
考虑下toast组件都有哪些功能,无非就是一些轻提示、loading等效果,可以在不同的时候调用不同的方法展示不同的内容。有了这个大方向,我们考虑最终使用toast采用如下方式:
Toast.success('成功‘)
Toast.error('失败’, 3000)
Toast.loading(true)
Toast.loading(false)
针对不容状态使用不同方法,参数分别为展示文字内容、展示时间、是否显示loading等,其中对于状态提示的两个参数均为可选,而loading方法是否展示为必填参数。同时我们将toast组件挂载到页面一个动态生成div里,直接通过css进行控制显示隐藏。
话不多说,直接上代码,以success为例:
import React, {Fragment} from 'react'
import ReactDom from 'react-dom'
//此处为toast展示的对应图片
import success from './images/success.png'
export default class Toast extends React.Component {
constructor(props){
super(props)
}
static success(mes="success", duration=2500){
ReactDOM.render(
<Fragment>
<div className="toast">
<div className="toast-box">
<img src={success} className="toast-icon" />
<span>{mes}</span>
</div>
</div>
</Fragment>,
document.getElementById('toast')
)
}
}
好了,基本的组件有了,但是我们发现他是静态的,而且页面上没有id=“toast”这个html元素,因此我们需要添加方法去让它动起来~
//这里我们添加两个方法,一个是初始化状态方法,一个是设置定时器方法
let timer = null;
//初始化方法
function init(){
clearTimeout(timer)
let toast = document.getElementById('toast')
if(toast){
toast.style.display = 'block'
}else{
let div = document.createElement('div')
div.setAttribute('id', 'toast')
document.body.appendChild(div)
}
}
//设置定时器
function setTimer(duration){
timer = setTimeout(()=>{
let toast = document.getElementById('toast')
if(toast){
toast.style.display = 'none'
}
}, duration)
}
//同时我们在组件的success方法里调用这两个方法
...省略内容
static success(mes="success", duration=2500){
init()
setTimer(duration)
...省略内容
}
...省略内容
好了,一个success toast就完成了,我们可以看下效果,点击按钮时调用Toast.sucess('领取失败')(为了查看效果,我临时添加了背景色)
这时我们发现,效果是正常的,但是貌似右侧出现了不河蟹的滚动条,这也是我们经常遇到的触发弹层禁止底部页面滚动问题,再添加对应方法消灭它
//添加两个方法,当组件触发时,对body添加类名同时保存滚动高度;组件隐藏时,删除类恢复滚动位置
let _scrollTop = 0
function popupOpen(){
_scrollTop = document.scrollingElement.scrollTop
document.body.style.top = -_scrollTop + 'px'
codument.body.classList.add('body-popup')
}
function popupClose(){
document.scrollingElement.scrollTop = _scrollTop
document.body.classList.remove('body-popup')
}
添加了两个方法后,效果perfect~~~
同样的道理,添加error和loading方法,注意loading方法需要有个status去判断是显示还是隐藏。完整代码奉上:
//toast.jsx
import React, { Fragment } from 'react'
import ReactDOM from 'react-dom'
import success from '../../../assets/images/toast_success.png'
import error from '../../../assets/images/toast_warning.png'
import loading from '../../../assets/images/loading.gif'
import './toast.scss'
let timer = null
let _scrollTop = 0
export default class Toast extends React.Component{
constructor(props){
super(props)
}
static success(mes='success', duration = 2500){
init()
setTime(duration)
ReactDOM.render(
<Fragment>
<div className="toast">
<div className="toast-box">
<img className="toast-icon" src={success} alt=""/>
<span>{mes}</span>
</div>
</div>
</Fragment>,
document.getElementById('toast')
)
}
static error(mes='fail', duration = 2500){
init()
setTime(duration)
ReactDOM.render(
<Fragment>
<div className="toast">
<div className="toast-box">
<img className="toast-icon" src={error} alt=""/>
<span>{mes}</span>
</div>
</div>
</Fragment>,
document.getElementById('toast')
)
}
static loading(status, mes=''){
init()
setLoading(status)
ReactDOM.render(
<Fragment>
<div className="toast">
<div className="toast-box">
<img className="toast-icon" src={loading} alt=""/>
<span>{mes}</span>
</div>
</div>
</Fragment>,
document.getElementById('toast')
)
}
}
//弹层出现时设置body类名,防止页面滚动
function popupOpen(){
_scrollTop = document.scrollingElement.scrollTop
document.body.classList.add('body-popup')
document.body.style.top = - _scrollTop + 'px'
}
//弹层隐藏时恢复滚动
function popupClose(){
document.body.classList.remove('body-popup')
document.scrollingElement.scrollTop = _scrollTop
}
//设置loading
function setLoading(status){
let toast = document.getElementById('toast')
if(status){
toast.style.display = 'block'
popupOpen()
}else{
toast.style.display = 'none'
popupClose()
}
}
//初始化toast
function init(){
clearTimeout(timer)
let toast = document.getElementById('toast')
if(toast){
toast.style.display = 'block'
}else{
let div = document.createElement('div')
div.setAttribute('id', 'toast')
document.body.appendChild(div)
}
popupOpen()
}
//设置定时器
function setTime(duration){
timer = setTimeout(()=>{
let toast = document.getElementById('toast')
if(toast){
toast.style.display = 'none'
popupClose()
}
}, duration)
}
//toast.scss
#toast{
position: relative;
z-index: 999;
}
.toast{
position: fixed;
left: 0;
top: 0;
bottom: 0;
right: 0;
.toast-box{
width: 240px;
height: 240px;
background: rgba(0, 0, 0, .6);
position: absolute;
left: 50%;
top: 50%;
margin: -120px 0 0 -120px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 10px;
.toast-icon{
width: 100px;
height: 100px;
margin-bottom: 20px;
}
span{
font-size: 28px;
color: #fff;
}
}
}
//body的类方法
body.body-popup{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
码字不易,对自己做个记录,同时希望对大家有所帮助~有不足之处欢迎指出~