阅读 405

知否 ?知否 ?React 插件了解一下!

首发地址

为什么选择插件,而不是组件?

  • 调用简单 this.$toast(“xxx”) ,不必再模板中提前定义 , 动态插入移除
  • 插件独立于业务
  • 更新不影响代码逻辑,做到热更新
  • 抽象逻辑
  • 适用于toast,Dialog,Alert,Message,picker,Actionsheet等组件

react-toast-alert demo地址

使用方法:

import $ from '@/component/Toast/index';

...

$.toast({
	type:0,
	content: "我是默认Toast",
	time: 1000,
	opacity: .5,
	onSucc() {
		console.log("我是Toast的回调!")
	}
 });
		
		
$.toast("我是默认Toast");

$.toast({
	type:3,
	content: "我是默认loading",
	time: 1000,
});

setTimeout(() => {  //3s后隐藏
	$.hide();
}, 3000);

$.dialog({
	type: 0,
	opacity:0.5,
	title: "我是title",
	content: "我是content",
	btnSucc: "我是成功",
	btnFail: "我是取消",
	onSucc(e) {
            e.stopPropagation();
            $.toast("我是默认Toast");
	},
	onFail(e) {
            e.stopPropagation();
            console.log("我是失败的回调!");
	}
});
复制代码

Vue 插件

在Vue里,一般将toast,alert等非业务相关的写成插件,挂载在Vue的原型链上,使用的时候直接this.$toast即可,非常方便!相关原理在这里就不说了,感兴趣的可以查阅官网. 先看看Vue的插件写法:

@/components/vue-toast/index.js

import ToastComponent from "./vue-toast.vue"; // 引入先前写好的vue
var Toast = {};
//避免重复install,设立flag
Toast.installed = false;
Toast.install = function(Vue, options = {
	type: "success", //success fail  warning loading toast
	msg: "操作成功",
	time: 1000,
	callback() {

	}
}) {
	if(Toast.installed) return;
	var obj;
	Vue.prototype.$toast = (config = {}, type) => {
		if(type == 'close') {
			obj && obj.removeToast();
			return false;
		}
		if(typeof config=="object"){
			config = {
				...options,
				...config
			}
		}else{
			config = {
				...options,
				...{
					type: "toast", 
	                msg: config
				}
			}
		}
		
		// 如果页面有toast则不继续执行
		if(document.querySelector('.vue-toast')) return;
		// 1、创建构造器,定义好提示信息的模板
		const toastTip = Vue.extend(ToastComponent);
		obj = new toastTip();

		for(var property in config) {
			obj[property] = config[property];
		}

		//删除弹框
		obj.removeToast = function() {
			document.body.removeChild(tpl);
		}

		//插入页面
		let tpl = obj.$mount().$el;
		document.body.appendChild(tpl);
		Toast.installed = true;

		if(['success', 'fail', 'warning','toast'].indexOf(config.type) > -1) {
			setTimeout(() => {
				obj.removeToast();
				obj.callback();
			}, config.time)
		}

		['close'].forEach(function(type) {
			Vue.prototype.$toast[type] = function(msg) {
				return Vue.prototype.$toast({}, type)
			}
		});
	};
};
// 自动安装  ,有了ES6就不要写AMD,CMD了
if(typeof window !== 'undefined' && window.Vue) {
	window.Vue.use(Toast)
};

export default Toast
复制代码

@/components/vue-toast/vue-toast.vue

<template>
  <div class="mask vue-toast">
    <div>
      <div class="toast" v-if="['success', 'fail', 'warning','loading'].indexOf(type) > -1">
        <div class="icon" v-if="['success', 'fail', 'warning'].indexOf(type) > -1">
          <div v-if="type=='success'" class="success-icon"></div>
          <div v-if="type=='fail'" class="fail-icon"></div>
          <div v-if="type=='warning'" class="warning-icon"></div>
        </div>
        <div class="loading-icon" v-else>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
          <span></span>
        </div>
        <div class="msg" v-html="msg"></div>
      </div>
      <div v-else class="toast-msg" v-html="msg"></div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      msg: " ",
      type: "success"
    };
  }
};
</script>
<style scoped="scoped " lang="less">
.mask {
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.4);
  position: fixed;
  left: 0px;
  top: 0px;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 10000;
  .toast-msg {
    font-size: 0.24rem;
    text-align: center;
    position: relative;
    transform: translateX(-50%);
    color: #fff;
    left: 50%;
    padding: 0.18rem 0.27rem;
    overflow: hidden;
    border-radius: 4px;
    white-space: nowrap;
    display: block;
    background: rgba(0, 0, 0, 0.7);
  }
  .toast {
    font-size: 0rem;
    padding: 0.27rem .090rem;
    width: 2.26rem;
    overflow: hidden;
    display: flex;
    align-items: center;
    flex-direction: column;
    color: #f2f2f2;
    background: rgba(51, 51, 51, 0.94);
    border-radius: 0.09rem;
    text-align: center;
    .icon {
      width: 0.72rem;
      height: 0.72rem;
      border-radius: 50%;
      border: 1px solid #f2f2f2;
      position: relative;
      justify-content: center;
      align-items: center;
      display: flex;
      
      position: relative;
      .success-icon {
        border-right: 1px solid #f2f2f2;
        border-bottom: 1px solid #f2f2f2;
        transform: rotate(45deg);
        width: 0.27rem;
        height: 0.45rem;
        margin-top: -0.18rem;
      }
      .fail-icon {
        &:before {
          content: " ";
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%) rotate(45deg);
          border-top: 1px solid #f2f2f2;
          width: 0.5rem;
          height: 0px;
        }
        &:after {
          content: " ";
          content: " ";
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%) rotate(-45deg);
          border-top: 1px solid #f2f2f2;
          width: 0.5rem;
          height: 0px;
        }
      }
      .warning-icon {
        &:before {
          content: " ";
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -500%) rotate(90deg);
          border-top: 1px solid #f2f2f2;
          width: 0.27rem;
          height: 0px;
        }
        &:after {
          content: " ";
          content: " ";
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, 250%) rotate(-45deg);
          background: #f2f2f2;
          width: 3px;
          border-radius: 50%;
          height: 3px;
        }
      }
    }
    .msg {
      margin-top: 0.18rem;
      font-size: 0.24rem;
    }
  }
}

.loading-icon {
  font-size: 0px;
  box-sizing: border-box;
  width: 0.72rem;
  height: 0.72rem;
  position: relative;
  span {
    position: absolute;
    height: 1px;
    left: 50%;
    top: 50%;
    width: 0.18rem;
    animation: loading-fade-light 1.1s infinite linear;
    background: rgba(255, 255, 255, 0.3);
    transform-origin: -0.18rem 50%;
    margin-left: 0.18rem;
    &:nth-child(1) {
      animation-delay: 0s;
      transform: rotate(0deg);
    }
    &:nth-child(2) {
      animation-delay: 0.1s;
      transform: rotate(30deg);
    }
    &:nth-child(3) {
      animation-delay: 0.2s;
      transform: rotate(60deg);
    }
    &:nth-child(4) {
      animation-delay: 0.3s;
      transform: rotate(90deg);
    }
    &:nth-child(5) {
      animation-delay: 0.4s;
      transform: rotate(120deg);
    }
    &:nth-child(6) {
      animation-delay: 0.5s;
      transform: rotate(150deg);
    }
    &:nth-child(7) {
      animation-delay: 0.6s;
      transform: rotate(180deg);
    }
    &:nth-child(8) {
      animation-delay: 0.7s;
      transform: rotate(210deg);
    }
    &:nth-child(9) {
      animation-delay: 0.8s;
      transform: rotate(240deg);
    }
    &:nth-child(10) {
      animation-delay: 0.9s;
      transform: rotate(270deg);
    }
    &:nth-child(11) {
      animation-delay: 1s;
      transform: rotate(300deg);
    }
    &:nth-child(12) {
      animation-delay: 1.1s;
      transform: rotate(330deg);
    }
  }
}

@-webkit-keyframes loading-fade-light {
  0% {
    background-color: #fff;
  }
  100% {
    background-color: rgba(255, 255, 255, 0);
  }
}

@keyframes loading-fade-light {
  0% {
    background-color: #fff;
  }
  100% {
    background-color: rgba(255, 255, 255, 0);
  }
}
</style>
复制代码

入口文件注册:

import vueToast from '@/components/vue-toast'
Vue.use(vueToast);
复制代码

React 插件toast的封装

Toast/index.js

import React from "react";
import ReactDOM from 'react-dom';
import Toast from './toast';
 
export default class Global {
	static toastEle='';
	static toast(option) {
		var setting={
			type:0,
		 	content:"默认信息",
		 	time:2000,
		 	opacity:0,
		 	onSucc:()=>{}
		};
		
		if(typeof option =="string"){
			setting={...setting,content:option,type:4}
		}else{
			setting={...setting,...option}
		}
		
		this.show(0,setting);
		
		if(setting.type!==3){   //loading需要手动关闭
			setTimeout(() => {
				this.hide();
				setting.onSucc();
			}, setting.time);
		}
	}
	
	static dialog(option) {
		 var setting={
		 	type:0,
		 	title:"我是默认title",
		 	content:"我是默认content",
		 	btnSucc:"我是默认btn",
		 	CloseShow:false,
		 	onClose(){
		 		console.log("蒙层回调");
		    },
		 	onSucc(){
		 		console.log("成功回调");
		    },
		    onFail(){
		    	console.log("失败回调");
		    }
		 };
		 
		 setting={...setting,...option};
		 
		 this.show(1,setting);
	}
   
	
	static show(n,setting) {
		
		var div = document.createElement('div');
		var id = document.createAttribute("id");
		
		this.toastEle='pluginEle-'+new Date().getTime();
		
		id.value = this.toastEle;
		div.setAttributeNode(id);
		document.body.appendChild(div);
		ReactDOM.render(<Toast setting={setting} />, div);
	}
	
	static hide() {
		var toastEle = document.querySelector("#"+this.toastEle);
		if(toastEle){
			ReactDOM.unmountComponentAtNode(toastEle);
		    document.body.removeChild(toastEle);
		}
	}
}
复制代码

Toast/toast.js

import React from "react";
import './toast.less'

export default class Toast extends React.Component {
	constructor(props) {
		super(props);
	}

	checkToast(n) {
		switch(n) {
			case 0:
				return (<div className="icon">
				          <div className="success-icon"></div>
			           </div>)
				break;
			case 1:
				return (<div className="icon">
				          <div className="fail-icon"></div>
			           </div>)
				break;
		    case 2:
				return (<div className="icon">
				          <div className="warning-icon"></div>
			           </div>)
				break;
		    case 3:
				return (
			            <div className="loading-icon">
				          <span></span>
				          <span></span>
				          <span></span>
				          <span></span>
				          <span></span>
				          <span></span>
				          <span></span>
				          <span></span>
				          <span></span>
				          <span></span>
				          <span></span>
				          <span></span>
				        </div>
			        )
				break;
			default:
			    return null
		}
	}

	render() {

		let {
			type,content,opacity = 0
		} = this.props.setting;

		let style = {
			"background": `rgba(0,0,0,${opacity})`
		}

		return(
			<div className="mask" style={style}>
			    <div className="toast">
			        {this.checkToast(type)}
			        <div className="msg">{content}</div>
			    </div>
			</div>
		);
	}
}
复制代码

Toast/toast.less

.mask {
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.4);
    position: fixed;
    left: 0px;
    top: 0px;
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 10000;
    .toast-msg {
        font-size: 24px;
        text-align: center;
        position: relative;
        transform: translateX(-50%);
        color: #fff;
        left: 50%;
        padding:18px 27px;
        overflow: hidden;
        border-radius: 4px;
        white-space: nowrap;
        display: block;
        background: rgba(0, 0, 0, 0.7);
    }
    .toast {
        font-size: 0px;
        padding: 27px 9px;
        width: 226px;
        overflow: hidden;
        display: flex;
        align-items: center;
        flex-direction: column;
        color: #f2f2f2;
        background: rgba(51, 51, 51, 0.94);
        border-radius: 9px;
        text-align: center;
        .icon {
            width:72px;
            height:72px;
            border-radius: 50%;
            border: 1px solid #f2f2f2;
            position: relative;
            justify-content: center;
            align-items: center;
            display: flex;
            position: relative;
            margin-bottom: 18px;
            .success-icon {
                border-right: 1px solid #f2f2f2;
                border-bottom: 1px solid #f2f2f2;
                transform: rotate(45deg);
                width:27px;
                height: 45px;
                margin-top: -18px;
                
            }
            .fail-icon {
                &:before {
                    content: " ";
                    position: absolute;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%) rotate(45deg);
                    border-top: 1px solid #f2f2f2;
                    width: 50px;
                    height: 0px;
                }
                &:after {
                    content: " ";
                    content: " ";
                    position: absolute;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%) rotate(-45deg);
                    border-top: 1px solid #f2f2f2;
                    width: 50px;
                    height: 0px;
                }
            }
            .warning-icon {
                &:before {
                    content: "";
                    display: block;
                    position: absolute;
                    top: 15px;
                    left: 50%;
                    background:#f2f2f2;
                    width:1px;
                    height: 30px;
                }
                &:after {
                    content: "";
                    position: absolute;
                    bottom: 14px;
                    left: 50%;
                    background:#f2f2f2;
                    width: 6px;
                    margin-left: -2px;
                    border-radius: 50%;
                    height: 6px;
                }
            }
        }
        .msg {
            
            font-size:24px;
        }
    }
}

.loading-icon {
    font-size: 0px;
    margin-bottom: 18px;
    box-sizing: border-box;
    width: 72px;
    height: 72px;
    position: relative;
    span {
        position: absolute;
        height: 1px;
        left: 50%;
        top: 50%;
        width: 18px;
        animation: loading-fade-light 1.1s infinite linear;
        background: rgba(255, 255, 255, 0.3);
        transform-origin: -18px 50%;
        margin-left: 18px;
        &:nth-child(1) {
            animation-delay: 0s;
            transform: rotate(0deg);
        }
        &:nth-child(2) {
            animation-delay: 0.1s;
            transform: rotate(30deg);
        }
        &:nth-child(3) {
            animation-delay: 0.2s;
            transform: rotate(60deg);
        }
        &:nth-child(4) {
            animation-delay: 0.3s;
            transform: rotate(90deg);
        }
        &:nth-child(5) {
            animation-delay: 0.4s;
            transform: rotate(120deg);
        }
        &:nth-child(6) {
            animation-delay: 0.5s;
            transform: rotate(150deg);
        }
        &:nth-child(7) {
            animation-delay: 0.6s;
            transform: rotate(180deg);
        }
        &:nth-child(8) {
            animation-delay: 0.7s;
            transform: rotate(210deg);
        }
        &:nth-child(9) {
            animation-delay: 0.8s;
            transform: rotate(240deg);
        }
        &:nth-child(10) {
            animation-delay: 0.9s;
            transform: rotate(270deg);
        }
        &:nth-child(11) {
            animation-delay: 1s;
            transform: rotate(300deg);
        }
        &:nth-child(12) {
            animation-delay: 1.1s;
            transform: rotate(330deg);
        }
    }
}

@-webkit-keyframes loading-fade-light {
    0% {
        background-color: #fff;
    }
    100% {
        background-color: rgba(255, 255, 255, 0);
    }
}

@keyframes loading-fade-light {
    0% {
        background-color: #fff;
    }
    100% {
        background-color: rgba(255, 255, 255, 0);
    }
}
复制代码

使用

import $ from '@/component/Toast/index';
...
$.toast({
	type:0,
	content: "我是默认Toast",
	time: 1000,
	opacity: .5,
	onSucc() {
		console.log("我是Toast的回调!")
	}
});
		
// $.toast("我是默认Toast");
复制代码

为什么不挂载在React原型链上?

有坑!主要原因还是组件的this神出鬼没!

demo 地址

关注下面的标签,发现更多相似文章
评论