第五集: 从零开始实现 (toast 组件)
本集定位:
toast 组件的实现, 争取一章就说完, 更多弹出相关的东西会在 alert 组件里面.
下章 loading 组件.
很开心昨天去现场录制 ’ 脱口秀大会 ’, 心情愉悦????.
一: toast 需求分析
- 弹出提示信息的形式出现, 与 alert 不同, toast 以简单提示为主.
- 不用用户操作 dom, 通过 js 操作即可 (比如封装 axios 的时候并没有 template 等标签).
- 优先级要可控, 默认要高一些, 毕竟提示要瞩目一些.
- 可自己消失, 可手动消失.
- 很常见的情况是用户疯狂点击, 或是事件多次触发, 处理好多弹出的状况与动画.
- 及时清除 dom 结构, 比如跳转的时候, 不要造成性能的损耗.
二: 基础结构的搭建
话不多少, 导出文件 Toast/index.js.
导出默认的引入项, 是可以如下简写的.
export {default} from './main/toast.js';
// 由于这个 js 文件内容比较重要, 我们放在后面讲, 先写结构
toast.vue 文件
<template>
// toast 弹框没有动画的话就太寒掺了.
// 我这边定义的是一个从上到下掉出来的效果.
<transition name="cc-toast-fade">
// 命名规则还是老样子, bem 原则.
<div class="cc-toast"
// 上面提到的 z-index 属性, 对于这种组件来说, 用户必须可控.
:style="'zIndex:'+zIndex">
// 用户可以随意插入内容
// 写成变量形式也可以, 这里用 slot 是为了见名知意.
<slot />
// 下面的是关闭的 'X' icon,
<i class="cc-toast__closeButton"
@click="deleteEl"
v-if="showButton">
<ccIcon name='cc-close'/>
</i>
</div>
</transition>
</template>
js 部分
import ccIcon from "@/components/Icon/main/icon.vue";
export default {
name: "toast",
components: {ccIcon},
props: {
zIndex: {
type: Number,
default: 10
},
showButton: {
// 是否要关闭的 x
type: Boolean,
default: true
},
}
}
三: 本人 z -index 的一些看法
从 z -index 的数值设定上就能看出一个人的做事风格, 比如我之前工作时候带过的几个人, 我甚至看到有些人经常写 999999 , 我不让他这样写, 之后他还跟我反应说, 不这样写不习惯????♀️, 我说如果要写比他等级更高的怎么办? 他说: 肯定不可能有那种情况.
其综合分析也就明白了, 喜欢这样去书写的人,
一是没有太长远的规划, 没有想到未来的多样性,
二是并没有为团队配合而考虑, 没有为他人书写代码 ’ 留有余地 ’ 而只顾自己.
本人独立负责的实际项目中试验过, 1-10 的 z -index 已经可以应付绝大多数工程了, 一个中小型项目分成 10 层真的算很多了, 这里面也有一些技巧, 比如说, 每次设置的时候要设置 1, 3, 5, 7, 9, 这是为了如果以后有特殊情况要放到某两个中间的, 可以留一个插入位, 随意约束好 z -index 是一个合格工程的开端.
四: scss 基本样式的设定
vue-cc-ui/src/style/Toast.scss
@import './common/mixin.scss';
@import "./common/animation.scss";
@include b(toast) {
position: fixed;
display: inline-block;
background-color: white;
top: 6px;
// 上方, 居中
left: 50%;
padding: 6px 20px;
transition: all .3s;
transform: translateX(-50%);
// 本套 ui 的精髓???? 黑边.
@include commonShadow($--color-black);
// 定义了动画的样子
@at-root {@include commonType(cc-toast--);
.#{$namespace}-toast--big {padding: 6px 35px 6px 20px;}
.#{$namespace}-toast-fade-enter,
.#{$namespace}-toast-fade-leave-active {
opacity: 0;
transform: translate(-50%, -100%);
}
};
}
五: 提示框的 type 颜色
提示框需要让用户能够很直观的分辨它的意义, 比如用户没有看到提示文字的时候, 但他看到了红色的提示框, 那他也会知道自己不应该进行刚才的操作.
属性当然就是 type 了
<div class="cc-toast"
:class="[toastType]" // 这里接收
:style="'zIndex:'+zIndex">
props: {
type: {
type: String,
default: "nomal"
},
计算属性里面我们还要规定一些合法值, 以及用户传错了的时候的默认值.
computed: {toastType() {
// 这里我做了 正常, 成功, 失败, 警告
let ary = ["nomal", "success", "warning", "danger"];
// 这里写三元也可以, 但不方便扩展, 万一以后要改那,,,,
// 其实无所谓, 我只是不想写那种长长的代码段
if (ary.includes(this.type)) {return "cc-toast--" + this.type;}
return "cc-toast--nomal";
}
},
scss
@at-root {@include commonType(cc-toast--);
};
之前写的 util 类, 循环加上彩色边框
@mixin commonType($name) {@each $type in (success, warning, danger) {.#{$name}#{$type} {@include commonShadow($type);
}
}
}
效果展示
6: 渲染到页面上 (说了这个才能引出如何关闭它)
vue-cc-ui/src/components/Toast/main/toast.js
这里我会写很详细的注释!!!
// 1: 把写好的组件引进来, 根据打包的规则, 其实引得已经是处理好的 js 代码了
import toast from './toast.vue';
export default {
// 2: 配合 use 方法
install(Vue) {
// 3: 为了保证全局都能控制弹出 toast, 我们只能原型编程
// 这个 options 就是将来我们调用他的时候, 传的参数.
Vue.prototype.$ccToast = function(options) {
// 4: 使用基础 Vue 构造器,创建一个“子类”。let Constructor = Vue.extend(toast),node;
// 5: 我们传入了配置项
if (typeof options === 'object' && options instanceof Object) {
// 6: 实例化这个组件
node = new Constructor({
// 7: 相当于你在定义 data 里面的数据
propsData: options
});
// 8: 提取需要弹出的信息到默认插槽
// 这里为什么要用数组我解释一下
// 因为这里我们传入的是 '结构'
// 所以他可能多个, 也可能是嵌套,
// 所以 vue 分析它必须要用数组的形式
// 也可以说他要的是 '子元素的集合'.
// 这方面有兴趣可以去看 render 的实现.
node.$slots.default = [options.message];
// 5: 我们传入一串字符串
} else if (typeof options === 'string') {
// 6: 直接实例化即可
node = new Constructor();
node.$slots.default = [options];
}
// 9: 调用函数的生命周期
node.vm = node.$mount();
// 10: 让他显示出出来
node.vm.visible = true
// 11: 无情插入
// 如果有特殊需求的同学, 不想让他插入 body, 而是通过传入来定也是不错的.
document.body.appendChild(node.$el);
};
}
};
7: 清除组件, 也可以说是关闭
- 用户点击 x 之后, 需要移除这个 dom 结构而不是隐藏
- 用户设置了不显示 x 则需要倒计时关闭
- 元素采用后者遮盖前者的方式显示.
- 思前想后没有定义 clearAll 这个函数, 原因如下:
之前我做的工程总是出现这样的问题, 比如一个人很谨慎, 他在写完一个提示框之后, 会写一个 clearAll(), 把所有的提示都清除, 这有涉及到了工程化, 组件定义的这个 clearAll 会无差别的清除所有显示的, 导致他清除了别人的提示信息, 而这种 bug 并不容易发现, 不是很喜欢这种太绝对函数.
x 绑定 click 事件.
<i class="cc-toast__closeButton"
@click="deleteEl"
v-if="showButton">
<ccIcon name='cc-close'/>
</i>
// 专门负责清理工作的函数
deleteEl() {
// 先触发动画效果, 不要直接 rm, 不然效果会很差.
this.visible = false;
setTimeout(() => {
// 下这样才能清理干净一个组件????
// 这里是去除元素
this.$el.remove();
// 完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。this.$destroy();}, 300);
},
scss
// 这个 'x' 还需要调节一下样式,
// 并且浪一下, 赋予 hover 时候疯狂旋转的动画, 充满朝气
.#{$namespace}-toast__closeButton {
display: flex;
cursor: pointer;
position: absolute;
align-items: center;
justify-content: center;
top: 0;
right: 0;
bottom: 0;
padding: 0 5px;
&:hover {animation: rotating .5s infinite linear;}
}
用户设置了自动关闭, 过期时间,
props: {
closeTime: {
// 关闭的延时
type: Number,
default: 2000
},
}
// 初始化是否设置了时间的操作
initAutoClose() {
// 万一用户传了个负数, 还是替他 abs 一下吧.
if (this.autoClose && Math.abs(this.closeTime) > 0) {
// 这里要用变量接一下, 方便停止他
this.timer = setTimeout(() => {this.deleteEl();
}, Math.abs(this.closeTime));
}
},
用户想详细看看提示内容, 把鼠标放在了 toast 上, 并不点击关闭
<div class="cc-toast"
:class="[
toastType,{'cc-toast--big':showButton}]":style="'zIndex:'+zIndex"
// 移入进来就别关闭倒计时了
@mousemove="clearTimer"
// 移出就从新走一遍倒计时初始化
@mouseleave="initAutoClose"
v-show="visible">
clearTimer() {clearTimeout(this.timer);
},
用户想点击键盘的 esc 键, 关闭这个组件
mounted() {this.initAutoClose();
// 开始就要添加对用户键盘事件的监听
document.addEventListener("keydown", this.keydown);
},
keydown(e) {if (e.keyCode === 27) {this.deleteEl();
}
}
// 既然有 document 的操作, 当然就要有去除的操作.
beforeDestroy() {
// 万一用户写了个很长的时间, 虽然 element 没有对此进行处理, 但我还是想写一下.
this.clearTimer()
document.removeEventListener('keydown', this.keydown);
}
end 到这里为止, 一个健康的 toast 也就可以正常使用了
感谢您的阅读, 喜欢的话就收藏吧, 让我们一起终生学习.
下章 loading 组件.
github:github
个人网站: 链接描述