面对面用Vue造一个Toast轮子(包含测试用例)

先上效果图...
toast.gif
面对面用Vue造一个Toast轮子(包含测试用例)
文章图片
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用户可点击关闭

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并没有变化,
关闭按钮被挤压
如下:

面对面用Vue造一个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; } }

面对面用Vue造一个Toast轮子(包含测试用例)
文章图片
改造后....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/

    推荐阅读