所有样式相关的都不要写在JavaScript里边,除非你有强烈的动机要去写!如组件开发者是否需要帮助用户设置toast的宽高?——不需要,让用户自行设置样式
前端在设计UI时,主要考虑的是可用性,如toast的关闭按钮是×还是直接一个大大的关闭按钮,前者很有可能用户无法点击到,而后者则可以点击到,虽然后者较为丑点,但是可以轻松被点击到。

芳芳设计的UI组件样式可以自行配色
用户要如何使用这个Toast?即如何调出一个Toast?——用户的做法(点击某个东西触发一个回调咯):
new Vue({
methods: {
showToast() {
//this.$toast('当前功能不稳定,如果遇到 bug 请关闭该功能')
}
}
})
我们应该为用户封装一个叫 $toast的API,当然,如果你怕用户记不住的话,你可以把它叫做 $msg
那么我如何才能做到 this上边有个 $toast呢?即如何改造this呢?——可以使用 Vue提供的installAPI,而该API甚是常用,比如常用于开发插件!
根据对比其它UI框架,发现用户不需要自行去 use插件,而是框架自己帮用户use的,所以我们也要这样来!
当然,还有一种更简单的做法,那就是改Vue 这个Class的prototype:
import Vue from 'vue'
Vue.prototype.$toast = function() {
console.log('我是 toast')
}
注意,在toast.vue里边,你要使用Vue,那么你得导入 Vue这个class(除了输出变量,还可以输出函数或类(class))
这样一来,只要是个Vue实例,那么就能使用 $toast这个API了。
使用webstorm的时候注意exclude一下,不然会很慢
工程问题(在工程项目中会实际发生的问题,这可不是你通过分析就能分析出来的)。如「用修改 prototype的姿势去为每个vm添加 $toast 」
这种姿势其实是很有问题的:
我们无法确定用户引入的 vue是哪个版本的,假如是 import Vue from 'vue2'呢?毕竟这是用户自行配置的——即便你认为这不99%不可能发生,但是根据墨菲定律,小概率事件必然会发生,所以你不得不防。
我们无法确定用户是否自行为Vue旗下添加了 $toast这个API,如果添加了,那么就会被我们覆盖,当然,如果你这样做的话:
if(Vue.prototype.$toast === undefined) {
Vue.prototype.$toast = function() {
console.log('我是 toast')
}
}
也是不行的。假如它确实不存在,那么还是不能解决我们强行修改的问题呀!总之,这样做的姿势是「侵入性太强」的做法,而这有可能会造成bug。
可见,我们改造this的这种姿势是违反工程的,如我们无法确定用户引入vue的姿势是我们所想的那样(假如用户是 import Vue from ‘vue1’,那么 import Vue from 'vue'就是无效的)、无法确定能不能修改全局Vue的prototype……
所以我们只好用插件姿势,而这也是为啥用插件的原因所在了。
那么这插件是干嘛的呢?——正如vue的文档所说,你应该写一个插件,然后让用户主动的去使用你,而这才是正确的方式!
在写轮子的时候一定要考虑一个定律——墨菲定律:

如果你不相信这个事件,那么你就存在侥幸心理,即自己骗自己,而程序不同人类社会活动,程序它本来就意味着逻辑严谨紧密的,所以只要存在小概率事件,你都得把它给解决掉!
install姿势:

以上就是告诉了你为啥要使用插件的姿势去做这件事了
接下来就是看看如何写这个 $toastAPI。
alert,但这很丑优化:
把alert变成是一个div
不用vue很简单,但是如果不用vue的话,那vue有啥用呢?

生成一个toast组件放到body里边(使用JavaScript动态创建一个toast组件,或者说是toast实例):

toast.$mount()可以让toast实例出现在内存里边,但没有出现在页面里边,所以我们需要把它append到页面里边。不加
toast.$mount的话,那么toast组件所有的生命周期钩子都不会执行,如toast.$el啥也没有。
toast.$slots.default是给Toast组件里的<slot></slot>放默认的内容的,而且是 数组姿势注意 `toast.$slots.default`一定要放在 `$mount`的前面
芳芳在写这个时候曾经写过博客介绍过,但是也忘记了,所以博客的作用是为了让你回忆的!
只看代码很简单,但是你得弄懂这背后之所以这样写的原因,如为啥 「
toast.$slots.default一定要放在$mount的前面」?
为动态生成的toast组件加样式:
translateX(-50%),之前我用的是 top: calc(50vh - 0.5em); 这样的姿势,显然前者要好很多,因为我无法确定这个div是否有padding之类的)目前初步达成了,接下来完善功能
效果:

this.$el.remove()(让元素从页面上被remove掉),当然你也可以用CSS姿势把自己给搞没掉,但是最好的方式是把自己彻底搞没掉,然后 this.$destroy()(把元素绑定的一些事件都给取消掉,反正它做了很多对内存友好的事儿,总之就是让组件死掉哈!)芳芳的代码实现起来是很少的,但这是他尝试了很多遍的结果
做法:
autoClose(默认true)、autoCloseDelay(默认5s)remove() 和 $destroy()一个很重要的结论:如果你的default值是个对象或者是数组,那么你得用函数姿势return一个对象或数组

那么为啥会这样呢?——因为不这样的话,会出现复用性的问题:

总之,你要搞清楚我们导出的是构造组件的选项,而你在利用这个构造选项创建组件实例的时候,都是引入同一个构造选项对象呀!所以closeButton用的default值是简单的一个普通对象的话,那么就是引用拷贝,即浅拷贝,而这样就会有bug的风险了,而函数姿势return一个值,是一个新的对象!总之,你不这样做,那么请后果自负
在芳芳提出怎么做的时候,请你先在脑海里边想一想该怎么做,而这样积累下来,差距就会逐渐拉大了。如:

芳芳上课提到的debug姿势都得记下来,如再created钩子里边log一下 this.closeButton,看看所传的两个属性也没有追加到vm实例里边去——突然觉得之所以用对象作为默认值,那是因为里边的key与key之间是配套使用的!
if……else判断一个变量存不存在,即为防御性编程——防止你的代码出现问题!
用户在传closeButton过来的时候,还可以使用组件提供的callback参数,它可以访问组件实例里的某些功能。

这是一个较为高级点的需求——用户想要callback能做点其它事情,如既然我能使用 $toast这个API,那么我传过去的callback,能不能使用 toast实例的一些methods呢?——因为是用户定义的callback函数,所以toast组件里边的是可以主动回传一个toast实例过去的。
做法:
closeButton(需要给个text和callback),那么就会展示这个按钮UI;如果用户点击了按钮,那么就执行 onClickClose自动关闭和主动关闭是共存的,默认是50s后就自动关闭,而自动关闭前,用户可以主动去关闭它,而点击关闭,就可以调用开发者给的callback。
总之,整体来看,我们都是围绕用户会怎么使用,然后不停地去想办法解决这个需求!
整体代码没有超过30行
一个提问而来的需求:
用户传过来的text是有标签的,如需要加粗……

<slot></slot>是个单独特性,它可无法使用 v-html,即无法这样 <slot v-html></slot>
经过权衡,折中的选择了一个办法
<slot></slot>了
父元素是个flex,所以子元素搞成是默认不缩的 flex-shrink:0;

造成这种现象的原因是「高度不能自适应」
解决字的容纳问题:
min-height然而这导致了另外一个bug,那就是子元素的 height:100%;(那根线)咩有办法根据父元素算了。不要问为什么会这样,它就是如此你有啥办法呢?
可以使用绝对定位,但是这线的位置变了。
所以使用JavaScript姿势。
主要用到 $refs这个属性
我们在mounted里边获取父元素的高度,然而这存在时间差的问题,即这是异步的,于是我们用了 $nextTick()这个办法拿到了父元素高度:

this.$refs.wrapper.style:返回一个CSSStyleDeclaration对象,表示元素的 内联style属性(attribute),但 忽略任何样式表应用的属性 。 通过style可以访问的 CSS 属性列表。所以你是无法读取到元素的height值的,我们一般通过这个属性来向DOM上加上内联的css属性,而不是修改自己原来写好的css属性
➹:demo
似乎是没有动态创建组件,并咩有出现异步情况!反正如果出现时间差的问题,那就异步执行一下呗!
➹:HTMLElement.style - Web API 接口参考 - MDN
➹:javascript - js中动态修改style属性的问题 - SegmentFault 思否
总之,min-height并不是说它是有高度的!所以儿子为百分比单位高度是不知道爸爸有多高的!
由于这个方法太tricky了,所以显得并不是很好呀!即看懂,所以我们应该重构了。
说白了是封装成两个函数呗——
为提示信息追加一个div,然后再加上padding,不然,如果你加到父元素身上的话,那根线是会有间隙。
其实这想想也是很有道理的:

我发现字体变大了,父容器的高度也变高了,但是那根线却咩有随着变高到一定程度,说白了,mounted执行一遍就GG了。
总之,CSS是最没有逻辑的,所以请不要问我为什么,多写多练就好了。
弹出3s后自动关闭:

主动关闭(为了效果明显,我设置自动关闭为6s,总之,如果你在6s之内不点「关闭」按钮,那么6s一到也会自动关闭):

根据用例图对比我们已经实现的功能!
上下的中间叫middle,左右的中间叫center
兼容性不好也是反工程的一种体现
includes这个API不支持IE,所以我们用了indexOf,因为它能支持IE9,总之谁兼容性好就用谁呗!
何时使用computed这个计算属性?——xx属性不是真实存在的,而是我们根据某个props属性计算出来的!
动态的key:
{
[`positon-${this.position}`]: true
}

数组的indexOf:该方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。第一个参数为要查找的元素,找到就返回索引,找不到就返回-1。所以只要返回 >=0的值,那么就可以断定xxx元素是在数组里边的。
总共10行代码,而CSS代码严格上来说不能算是代码,而template的可以算是代码!
效果:

**①如果已经有一个toast,那就把之前的给干掉,然后再创建新的**
如何知道之前的在哪儿?——找到你创建Toast的代码
重构了创建Toast的代码——创建函数,提取形参,解析赋值形参
如何知道这是新创建的Toast?

效果:

commit:实现了只能有一个toast的功能
实现多个toast的之后再做,因为目前来看这性价比不高,而实现动画的则性价比更高!
**②实现动画,因为toast的弹出和消失是很突兀的**
直接使用CSS实现即可!
如何做进入的动画?
使用transform会出现跳跃现象!

额为的优化:
由于我们第一次出现toast时是不需要close的,所有我们搞了个回调,即第一次出现不会去close。
webstorm有个功能是,你在提交的时候,如果做了两个不相关的操作,是可以把它们俩独立分开来提交的,即由一次提交变为两次提交:
跳跃现象出现的原因:

解决姿势:
transform:translateX(-50%);来做居中,但这是最好的居中方式,如果换一种的话,得浪费很多脑细胞去想怎么居中在添加一层div的时候,如果你没有写子代选择器是没有问题的!但是使用了flex会GG—— &的好处可以让你在嵌套div的时候,直接复制粘贴即可,而不需要修改选择器。
芳芳在编程的时候有个习惯是「这是对的吗?」,(你要做出判断选择吗?)不需要,直接说「当它是对的先,如果出bug了,那就当它错呗!」
有人说到「可以在动画的两个帧上,都加上 transform:translateX(-50%); 」
我们并不知道为toast加动画时,它是怎么居中的。
总之,如果按照这种写法的话,那么你就得永远背下一个规则:「我每次加动画的时候,都得加上 transform:translateX(-50%); 」,你必须背下来,不然这就是bug了。
而如果像出现这种「必须背下来,不背下来就会出现bug的代码」,那么这个代码本身就是bug。
总之,不要去背这样的东西!
或许有些东西需要去背?比如内联元素不对齐的问题,然后
vertical之类的……或许这个动画出现的场景并不多,即性价比不高,背下来也就是bug咯!
还是出现了bug——测试toast居中的情况,没有真正的居中,这个你可以把vh的高度减小即可看出来:

可见,这是toast的上边沿居中
解决姿势:

总之,你在修改 `.warpper`的样式时,需要你去看原来的样式,毕竟这是互相影响的。
测试top/middle/bottom的效果:

**③优化toast为bottom时,左下角和右下角是方的,而这是根据设计图而来的**
**④再优化,为top的toast应该是从上往下出现的,而不是像为bottom的toast从下往上出现的**
关于动画的时间,我们需要做一个变量,一般它的值建议是300ms或者是250ms。
效果:

commit:优化三种动画

1s后,触发vm的close方法,而close里边有个
this.$emit('close'),所以close事件被执行了,此时toast元素已经remove了可见,这代码技巧性十足啊!
本来这代码是这样的:

传了autoClose,又得传autoCloseDelay有点冗余了,能不能只写一个参数呢?即autoClose即表示要自动关闭,也表示多少秒后自动关闭


console.log(vm.$el.outerHTML)
测试这个的时候,出现了警告呀!

这来自于 autoClose的默认值为 true,其实这应该是Number类型呀,比如 5这样
这个测试思路来自于,如果选择器能选到该元素,那么就是作为一个元素而存在的。
每次测试都得要搞一个错误的结果才行,不然有时候测试代码是错的也通过了。

思路:就看有没有相应的class在元素身上
如果你使用watched姿势测试的,很容易会出现错误的,如果出现错误最好重启一下!
commit:完成测试用例
之后会讲到测试覆盖率,目前只是把属性和事件测一测就行了。
$el.outerHTMLthis值,这样就可以访问组件实例的method,而这样一来,但用户点击关闭,执行该callback就会有更多的功能更多的操作了。.vue文件时,把 script标签写在 style标签前面呢?因为CSS是最不重要的!
而且我们很多时候写CSS都喜欢换行,导致CSS代码所占行数过多。
可见,如果把 style标签写在前面的话,那么我们写script的时候,就得每次回顾CSS代码了。
毕竟 更新JavaScript和HTML代码的频率要比CSS高得多!
所以为了减少肉眼负担,提高书写代码效率,只好这样做了。
换言之,是create一个组件好,还是create一个div好?
如果你用了vue,请一定优先create一个组件而不是一个ui。
一个明显的好处是,我们的组件可以使用 @click这种方式绑定事件
而div姿势,则是需要这样 div.addEventListener('click',callback),可见这真TM麻烦啊!
总之,你既然引入了vue,那就用vue吧!毕竟你既然引入了vue,为啥不去充分使用它呢?
不写的话,destroy并不能够把元素从页面中移除!
所以这需要自己删掉呀!
这是测试出来的结果!

➹: demo
➹:vue 生命周期函数 - weixin_43208813的博客 - CSDN博客
➹:2019.7.15 - 7.21 中你学到什么? · Issue #1 · KieSun/today-i-learned
为啥需要插件?——因为我需要为 Vue 添加全局功能
如:
Vue.prototype 里边实现。那么如何使用插件呢?
通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成
// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
new Vue({
// ...组件选项
})
Vue.use(MyPlugin)相当于是 MyPlugin.install(Vue)
如何开发一个插件?
install 方法
Vue 构造器如:添加实例方法
MyPlugin.install = function (Vue, options) {
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
测试:
搞一个可以创建Toast组件的插件:
let plugin = {
install(Vue,options) {
Vue.prototype.$toast = (message) => {
let Constructor = Vue.extend(Toast)
let toast = new Constructor()
toast.$slots.default = [message]
toast.$mount()
document.body.appendChild(toast.$el)
}
}
}
使用:
Vue.use(plugin) //plugin.install(Vue) or plugin.install(Vue,options)
消息传递:
person.cut('jj'):
- 给 persom 对象发送了一个 cut 消息
- person 对象会响应这个消息
这是函数调用姿势,下边这个是另一种语言的语法
Smalltalk :
person cut: 'jj'所以
plugin.install(Vue):
- 给 plugin 对象发送个 install 消息
- plugin 对象会响应这个消息
plugin install: Vue假装理解
Vue.use(plugin):class Vue { static use(xxx) { return xxx.install(this) } } Vue.use(plugin)
toast.$slots.default = [message]?动态为组件的插槽赋值?➹: demo
ps:

transform: translate(-50%, -50%);的作用?
一般都是配合定位来搞事情的。

总之,写一个数字3相当于是两层意思,一层是我想要自动关闭,另一层是3s后关
如:toast元素里边的那根线的高度,我们是根据JavaScript计算出来的
describe('CSS', function () {
it('line 的高度', (done) => {
const Constructor = Vue.extend(Toast)
const vm = new Constructor({
propsData: {
position: 'middle',
closeButton: {
text: '已充值',
callback() {
console.log('他说已经充值智商了!')
}
},
autoClose: false,
}
}).$mount()
let line = vm.$el.querySelector('.line')
let toast = vm.$el.querySelector('.toast')
document.body.appendChild(vm.$el)
setTimeout(()=>{
expect(line.style.height).to.eq(`${toast.getBoundingClientRect().height}px`)
done()
vm.$el.remove()
vm.$destroy()
})
})
})
测试是通过了,但是报错了:

表示找不到解决方案。
Vue.config.errorHandler = done?
➹:fix(toast): 添加toast的测试用例 · zyqq/wheel@cf74c2b
是在测试代码里边使用了Vue.nextTick才配置好吧!