阅读 503

🚩记一次高级Vue工程师的面试

今天参加一场面试,印象比较深刻,面试结束后做了个总结,同大家分享一下。如果对您有帮助,点个赞支持一下呗。

应聘的是高级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-enterloading-leave-toloading-enter-activeloading-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;
    }
}
复制代码

先利用borderborder-radiusCSS属性画个圆圈,然后边框颜色按上右下左顺序设置相邻颜色。再利用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 记。