今天参加一场面试,印象比较深刻,面试结束后做了个总结,同大家分享一下。如果对您有帮助,点个赞支持一下呗。
应聘的是高级Vue工程师的岗位,很奇特的岗位,想想自己也用Vue三年多了,应该算是高级的吧,待遇在这座城市还不错,十几k,15薪。
面试官是一位7年经验的大佬了。面试中问得都是关于Vue和Webpack的问题,还真是要招个高级Vue工程师。
经过Vue API方面、原理方面、Webpack配置方面一个小时半的拷打,本想应该结束了,没想到面试官指了我背包,说有带电脑吧,我下意识回答有。“那你现场写个简单的组件吧,充电器有带吧?” 真是嘴欠,发誓下次面试再也不背包带电脑了。为了钱,只好认命拿出电脑,问道:“什么需求呢?”
“简单实现一个加载动画的组件,动画特效用CSS3开发,组件显示隐藏时要有过度效果,可以用this.$loading(options)调用,也可以用v-loading调用。时间1个小时,够吗?”
“用this调用,用v-loading调用,当时挺紧张,一开始也没什么思路。”
假装镇定的,打开VS Code,其实内心慌得一批,之前压根没写过这种组件,只记得element-ui有这么调用过,要么去项目中看看element-ui源码,还是算了吧,大佬坐在我后面。只好静下心一步一步来。
一、实现加载动画和显示隐藏过渡效果
加载动画用CSS3的animation
属性,过渡效果用Vue 内置组件transition
来实现。
首先是页面布局,这个简单,很快就写出来
//loading.vue
<template>
<transition name="loading">
<div v-show="isShow" class="ui-loading-wrap">
<div class="ui-loading-mask"></div>
<div class="ui-loading">
<div class="ui-loading-icon"></div>
<div class="ui-loading-text">{{ text }}</div>
</div>
</div>
</transition>
</template>
<script>
export default {
props:{
text:{
type:String,
default:'加载中'
},
isShow:{
type: Boolean,
default: false
}
}
}
</script>
然后处理一下。显示隐藏的过渡效果。transition
内置组件的name
默认值为v
,用于自动生成 CSS 过渡类名,如
v-enter
:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。`v-leave-to
:定义离开过渡的结束状态。在过渡/动画完成之后移除。v-enter-active
:定义进入过渡生效时的状态。v-leave-active
:定义离开过渡生效时的状态。
将name
设置为loading,则会生成loading-enter
、loading-leave-to
、loading-enter-active
、loading-leave-active
,可以利用这些className来做一下过度特效,代码如下
//loading.vue
.loading-enter-active {
transition: all 0.3s ease;
}
.loading-leave-active {
transition: all 0.3s ease;
}
.loading-enter,
.loading-leave-to {
opacity: 0;
}
然后将页面布局样式写一下,这里利用到flex布局让加载动画居中。
//loading.vue
ui-loading-wrap{
position: absolute;
z-index: 999;
top: 0px;
bottom: 0;
left: 0;
right: 0;
}
.ui-loading-mask {
height: 100%;
background-color: #000;
opacity: 0.2;
}
.ui-loading {
position: absolute;
z-index: 1001;
top: 0px;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.ui-loading-text {
color: #ffffff;
}
}
先利用border
和border-radius
CSS属性画个圆圈,然后边框颜色按上右下左顺序设置相邻颜色。再利用animation
和@keyframes
,让圆圈转起来,就完成加载动画。
//loading.vue
.ui-loading-icon{
width: 40px;
height: 40px;
margin-bottom: 20px;
border-radius: 50%;
border-top:3px solid #c7f7e8;
border-right:3px solid #c6ffed;
border-bottom:3px solid #b4f8e3;
border-left:3px solid #a0fadd;
animation:turn 1s linear infinite;
}
@keyframes turn{
0%{-webkit-transform:rotate(0deg);}
25%{-webkit-transform:rotate(90deg);}
50%{-webkit-transform:rotate(180deg);}
75%{-webkit-transform:rotate(270deg);}
100%{-webkit-transform:rotate(360deg);}
}
上面内容只考察前端的基操,很快就写出来,回头看了下面试官,他面无表情地点了点头,说继续。唉,下面就不知道怎么写了,怎么用this.$loading(options)
,陷入沉思中。
二、实现this.$loading(options)调用
this
代表的是Vue,那么$loading
应该是挂载到Vue.prototype
上的。当时我想10了分钟,丝毫没有头绪。在快要放弃的时候,突然想到loading组件生成的DOM元素是插入到引入页面的根节点上,那只要在$loading这个方法中获取到loading组件生成的DOM元素把其插入相应的元素中即可。
记得Vue中有个实例属性vm.$el
可以获取DOM元素,不过总不能在$loading
方法中再new个Vue吧,应该要创建一个子类继承Vue类。
创建子类,隐隐约约记得有个Vue.extend
的全局API好像是继承Vue类创建一个子类的。就是基于Vue构造器生成一个子构造器,子构造器拥有Vue构造器的一切方法。
说实话,Vue.extend
这个API在平时工作中比较少用到,只记得其参数是一个对象。如果要用Vue.extend
创造出来的构造器Loading,然后在new Loading()
得到一个实例loading,再通过loading.$el
得到loading组件生成的DOM元素。那么Vue.extend
的参数应该是loading组件的export default
的对象吧,姑且算是吧,想的时间有点久了。
思路有了,在写之前要把原先写的loading组件改造一下。因为接下来用到是由loading组件生成的实例loading,要让loading组件显示,不能在通过props传参将isShow
置为true,可以直接通过loading.isShow = true
实现。
//loading.vue
<script>
export default {
data:{
text:{
type:String,
default:'加载中'
},
isShow:{
type: Boolean,
default: false
}
}
}
</script>
新建一个loading.js文件。
UiLoading
为loading组件的export default
的对象吧,用Vue.extend(UiLoading)
创造出构造器Loading。
封装一个方法LoadingInit,用new Loading()
生成loading组件实例loading
,将loading.$el
(loading组件生成DOM)插入到body,使用 Vue.nextTick
在页面渲染完成后loading.isShow
置为true,加载动画组件就显示出来。
把方法LoadingInit挂载在Vue原型上Vue.prototype.$loading = LoadingInit
,这样就可以通过this.$loading(options)
调用。
再写个关闭loading组件的方法,很简单,关闭时把loading组件生成的DOM从父级元素(this.$el.parentNode
)中移除,再调用this.$destroy()
,销毁loading组件就可以了。
具体代码实现如下。
//loading.js
import Vue from 'vue';
import UiLoading from './loading.vue';
const Loading = Vue.extend(UiLoading);
let loading = undefined;
Loading.prototype.close = function () {
if (loading) {
loading = undefined
}
this.isShow = false;
setTimeout(() => {
if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el);
}
this.$destroy();
}, 300)
}
const LoadingInit = (options = {}) => {
if (loading) {
loading.close();
}
let parent;
if(options.parent && Object.prototype.toString.call(options.parent) == '[object String]'){
parent = document.querySelector(options.parent);
}else{
parent = document.body
}
loading = new Loading({
el: document.createElement('div'),
data: options
})
parent.appendChild(loading.$el);
Vue.nextTick(() => {
loading.isShow = true;
})
return loading;
}
Vue.prototype.$loading = LoadingInit;
export default LoadingInit
写完在main.js文件引入loading.js。试着调用一下,加载动画组件正常显示隐藏。看一下时间已经过去四十多分钟,来不及去看面试官什么表情,抓紧实现一下v-loading调用。
三、实现v-loading调用
懂得怎么实现用this.$loading(options)
调用loading组件,实现用v-loading调用loading组件就很简单了。新建一个v_loading.js文件。
老样子,先用Vue.extend(UiLoading)创造出构造器Loading。
在bind
钩子函数中,其第一个参数el
,是绑定v-loading
指令所绑定的元素,可以用来直接操作DOM,第二个参数binding
中能获取到指令绑定的值。
照样用new Loading()
生成loading组件实例loading
,将loading.$el
(loading组件生成DOM)插入到el
中,同时将实例loading
挂载到el.loading
上做个缓存。使用 Vue.nextTick
在页面渲染完成后调将指令绑定的值binding.value
赋值给loading组件中的el.loading.isShow
。
在update
钩子函数中,当指令绑定的值发生变化时,将binding.value
赋值给el.loading.isShow
,就可以控制loading组件的显示和隐藏。
最后在unbind
钩子函数中,当指令解绑时,将loading组件生成的DOM从el.loading.$el.parentNode
上移除。同时调用el.loading.$destroy()
销毁loading组件,el.loading
置为null,解除内存占用。
具体实现代码如下。
import Vue from 'vue'
import UiLoading from './loading.vue'
const Loading = Vue.extend(UiLoading)
Vue.directive('loading', {
bind(el, binding) {
const loading = new Loading({
el: document.createElement('div'),
data: {}
})
el.appendChild(loading.$el);
el.loading = loading;
Vue.nextTick(() => {
loading.isShow = binding.value;
})
},
update(el, binding) {
if (binding.oldValue !== binding.value) {
el.loading.isShow = binding.value;
}
},
unbind(el) {
const element = el.loading.$el;
if (element.parentNode) {
element.parentNode.removeChild(element);
}
el.loading.$destroy();
el.loading = null;
}
})
写完在main.js文件引入v_loading.js。试着调用一下,加载动画组件正常显示隐藏。
四、后记
写完看一下时间,56分钟,差4分钟就超过一个小时。面试官看一下页面,说:“代码打包一下发我邮箱,接下来人事再跟你聊一下。”
跟人事聊了一下,福利待遇方面话题,人事最后说:“五个工作日内给你答复”。面试就结束了。
回到家,回想这个面试过程。前面回答Vue API方面、原理方面、Webpack配置方面没有过多的卡顿,很流畅,就是最后写loading 组件过程中卡了十几分钟,让面试官感觉有点不太熟练。
Vue.extend
这个API在平时工作中真是较少用到,不过最后还是有实现,希望有个好结果吧,有点患得患失。
2020年08月17日 21:54 记。