面对面用Vue造一个Toast轮子(包含测试用例)
先上效果图...
toast.gif
文章图片
toast.png Toast需求:
- 弹出Toast自动关闭
- 多少(N)秒后自动关闭
- 弹出Toast用户可点击关闭
- 用户点击关闭后回调(fn)
- 保证只有一个Toast弹出,不会同时出现两个。
import Vue from 'vue'
import plugin from './plugin/index'
Vue.use(plugin)this.$toast('操作成功!!!')
Github:欢迎Star 这里是分割线
开始面对面,看代码...
不管在哪里都可以直接使用
this.$toast
,具体如何实现??? 请看Vue官方文档1、main.js
Vue.prototype.$toast = function () {
console.log('我是苏宋霖');
}
2、App.vue
export default {
name: 'app',
methods: {
xxx() {
this.$toast()
},
}
}
点击按钮控制台是不是打印出了
我是苏宋霖
,做到这里你已经成功一半了clickToast.gif
但是这样并不是我们想要的,使用插件形式
install
,让用户主动去使用。继续改造? 新建plugin/index.js
export default {
install(Vue,options){
Vue.prototype.$toast = function(message){
console.log(message);
}
}
}
main.js
import plugin from './plugin/index'
Vue.use(plugin)
使用
this.$toast('我是苏宋霖')
控制一样打印出
我是苏宋霖
, 说明你距离成功不远了 (这句话跟高中老师一样,经常对我们学生说前脚已经跨进北大的校门了,后来我们毕业了才知道北大是可以随便进出,哪怕登记一下就好)Vue.use
会自动阻止多次注册相同插件,届时即使多次调用也只会注册一次该插件。继续改造?? 新建components/Toast.vue
在引入Toast.vue之前有个问题如何在js使用vue实例 ??
1、方应杭的Vue 动态创建实例
2、滴滴的cube-ui专门为这个场景实现了一个create-api, 可以将任意自定义组件制作成调用时动态创建的插件
plugin/index.js 引入Toast.vue
import Toast from '@/components/Toast.vue'
export default {
install(Vue,options){
Vue.prototype.$toast = function(message){
let Constructor = Vue.extend(Toast)
let toast = new Constructor()
toast.$mount()
document.body.appendChild(toast.$el)//添加到页面中
}
}
}
通过代码可以实现动态创建实例(别用原生js实现如:
let div = document.createElement("div");
document.body.appendChild(div)
,这样无法使用到vue的各种生命周期及Api)appendChildToast.gif 继续改造??? Toast.vue 文件不变,index.js通过插槽传值给Toast.vue
问题:如果在js中使用插槽传值啊??? 我的天
看文档渲染函数 & JSX
this.$slots.default // 子节点数组
注意这里接收的是数组 数组 数组import Toast from '@/components/Toast.vue'
export default {
install(Vue,options){
Vue.prototype.$toast = function(message){
let Constructor = Vue.extend(Toast)
let toast = new Constructor()
toast.$slots.default = [message] //slots必须要放在mount之前
toast.$mount()
document.body.appendChild(toast.$el)
}
}
}
slots.gif 添加简单样式
$font-size:14px;
$toast-height:40px;
$toast-bg:rgba(0, 0, 0, 0.75);
.toast {
font-size: $font-size;
line-height: 1.8;
height: $toast-height;
position: fixed;
top: 0;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
color: white;
background: $toast-bg;
border-radius: 4px;
box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, 0.50);
padding: 0 16px;
}
继续改造???? 实现3s后自动关闭,
props: {
// 自动关闭
autoClose: {
type: Boolean,
default: true
},
// 关闭时间
autoCloseDelay: {
type: Number,
default: 3
}
},
mounted() {
if (this.autoClose) {
setTimeout(() => {
this.close();
}, this.autoCloseDelay * 1000);
}
},
methods: {
close() {
this.$el.remove();
//删除
this.$destroy();
//清除绑定的一些事件
}
}
autoClose.gif 继续改造????? 弹出Toast用户可点击关闭
{{closeButton.text}}
plugin/index.js
import Toast from '@/components/Toast.vue'
export default {
install(Vue,options){
Vue.prototype.$toast = function(message,toaseOptions){
let Constructor = Vue.extend(Toast)
let toast = new Constructor({
propsData:toaseOptions
})
toast.$slots.default = [message]
toast.$mount()
document.body.appendChild(toast.$el)
}
}
}
使用
this.$toast('我是苏宋霖',{
closeButton:{
text:'知道了',
callback(){
console.log('苏宋霖点击知道了');
}
}
})
closeButton.gif 手动测试发现一个问题,内容过多的时候Toast并没有变化,
关闭按钮被挤压
如下:
文章图片
溢出.png 改造吧支持多行文字
使用js去给line添加高度,因为父元素的高度变成min-height。
//html
{{closeButton.text}}
//js
mounted(){
this.updateStyle()
},
methods: {
updateStyle() {
this.$nextTick(() => {
this.$refs.line.style.height =
`${this.$refs.toast.getBoundingClientRect().height}px`
})
},
}
//css 新增的$toast-min-height: 40px;
.toast {
min-height: $toast-min-height;
.close {
flex-shrink: 0;
}
}
文章图片
改造后....png 继续改造??????
Toast的位置
在.toast上加
:class="toastClasses"
,通过props传过来的属性position
动态添加class//html
{{closeButton.text}}
//js
props:{
// 位置
position: {
type: String,
default: "top",
validator(value) {
return ["top", "bottom", "middle"].indexOf(value) >= 0;
}
}
}computed: {
toastClasses() {
return {
[`position-${this.position}`]: true
};
}
},
//css
&.position-top {
top: 0;
transform: translateX(-50%);
}&.position-bottom {
bottom: 0;
transform: translateX(-50%);
}
&.position-middle {
top: 50%;
transform: translate(-50%, -50%);
}
使用
position.gif 新问题:
用户重复点击Toast,会出现多个DOM??
重复DOM.gif 解决方案:
如果已经有一个Toast,就把之前的给删了
plugin/index.js
import Toast from '@/components/Toast.vue'export default {
install(Vue,options){
let currentToast ;
Vue.prototype.$toast = function(message,toaseOptions){
if(currentToast){currentToast.close()}//如果有Toast就删除上一个
currentToast =createToast({Vue,propsData:toaseOptions,message})
}
}
}function createToast({Vue,propsData,message}) {
let Constructor = Vue.extend(Toast)
let toast = new Constructor({ propsData})
toast.$slots.default = [message]
toast.$mount()
document.body.appendChild(toast.$el)
return toast
}
DOM正常.gif ps:样式啥的请自行修改。
轮子完毕!!! 接下来测试用例.. 未完待续... 提示: 【面对面用Vue造一个Toast轮子(包含测试用例)】npm 配置淘宝源:npm config set registry https://registry.npm.taobao.org/
推荐阅读
- Docker应用:容器间通信与Mariadb数据库主从复制
- JS中的各种宽高度定义及其应用
- 由浅入深理解AOP
- vue-cli|vue-cli 3.x vue.config.js 配置
- 【译】20个更有效地使用谷歌搜索的技巧
- 2020-04-07vue中Axios的封装和API接口的管理
- 涉毒患者(新诗)
- 参保人员因患病来不及到指定的医疗机构就医,能否报销医疗费用()
- mybatisplus如何在xml的连表查询中使用queryWrapper
- MybatisPlus|MybatisPlus LambdaQueryWrapper使用int默认值的坑及解决