题目分析:vue是组件化开发框架,所以对于vue应用来说组件间的数据通信非常重要。此题主要考查大家vue基本功,对于vue基础api运用熟练度。另外一些边界知识如provide/inject/listeners则体现了面试者的知识面。
父子组件
props
$emit
/$on
$parent
/ $children
ref
$attrs
/ $listeners
兄弟组件
$parent
eventbus
vuex
跨层级关系
provide
/inject
$root
eventbus
vuex
此题考查实践能力,能说出用法只能60分。更重要的是对vuex设计理念和实现原理的解读。
首先是官网定义:
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
此题是考查项目实践能力,项目中基本都有路由守卫的需求,保护指定路由考查的就是这个知识点。
router.beforeEach((to,from,next)=>{})
方式设置守卫,每次路由导航时,都会执行该守卫,从而检查当前用户是否可以继续导航,通过给next函数传递多种参数达到不同的目的,比如如果禁止用户继续导航可以传递next(false),正常放行可以不传递参数,传递path字符串可以重定向到一个新的地址等等。能不能说说全局守卫、路由独享守卫和组件内守卫区别?
作用范围
组件实例的获取
beforeRouteEnter(to,from,next) { next(vm => { }) }
名称/数量/顺序
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的
beforeEach
守卫。- 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。- 在路由配置里调用
beforeEnter
。- 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。- 调用全局的
beforeResolve
守卫 (2.5+)。- 导航被确认。
- 调用全局的
afterEach
钩子。- 触发 DOM 更新。
- 用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调函数。
你项目中的路由守卫是怎么做的?
前后端路由一样吗?
前端路由是用什么方式实现的?
你前面提到的next方法是怎么实现的?
这是一道综合实践题目,写过一定数量的代码之后小伙伴们自然会开始关注一些优化方法,答得越多肯定实践经验也越丰富,是很好的题目。
根据题目描述,这里主要探讨Vue代码层面的优化
我这里主要从Vue代码编写层面说一些优化手段,例如:代码分割、服务端渲染、组件缓存、长列表优化等
最常见的路由懒加载:有效拆分App尺寸,访问时才异步加载
const router = createRouter({ routes: [ // 借助webpack的import()实现异步组件 { path: '/foo', component: () => import('./Foo.vue') } ] })
keep-alive
缓存页面:避免重复创建组件实例,且能保留缓存组件状态
<router-view v-slot="{ Component }"> <keep-alive> <component :is="Component"></component> </keep-alive> </router-view>
使用v-show
复用DOM:避免重复创建组件
<template> <div class="cell"> <!-- 这种情况用v-show复用DOM,比v-if效果好 --> <div v-show="value" class="on"> <Heavy :n="10000"/> </div> <section v-show="!value" class="off"> <Heavy :n="10000"/> </section> </div> </template>
v-for
遍历避免同时使用 v-if
:实际上在Vue3中已经是个错误写法
<template> <ul> <li v-for="user in activeUsers" <!-- 避免同时使用,vue3中会报错 --> <!-- v-if="user.isActive" --> :key="user.id"> {{ user.name }} </li> </ul> </template> <script> export default { computed: { activeUsers: function () { return this.users.filter(user => user.isActive) } } } </script>
v-once和v-memo:不再变化的数据使用v-once
<!-- single element --> <span v-once>This will never change: {{msg}}</span> <!-- the element have children --> <div v-once> <h1>comment</h1> <p>{{msg}}</p> </div> <!-- component --> <my-component v-once :comment="msg"></my-component> <!-- `v-for` directive --> <ul> <li v-for="i in list" v-once>{{i}}</li> </ul>
按条件跳过更新时使用v-momo
:下面这个列表只会更新选中状态变化项
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]"> <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p> <p>...more child nodes</p> </div>
长列表性能优化:如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容
<recycle-scroller class="items" :items="items" :item-size="24" > <template v-slot="{ item }"> <FetchItemView :item="item" @vote="voteItem(item)" /> </template> </recycle-scroller>
一些开源库:
事件的销毁:Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。
export default { created() { this.timer = setInterval(this.refresh, 2000) }, beforeUnmount() { clearInterval(this.timer) } }
图片懒加载
对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。
<img v-lazy="/static/img/1.png">
参考项目:vue-lazyload
第三方插件按需引入
像element-plus
这样的第三方组件库可以按需引入避免体积太大。
import { createApp } from 'vue'; import { Button, Select } from 'element-plus'; const app = createApp() app.use(Button) app.use(Select)
子组件分割策略:较重的状态组件适合拆分
<template> <div> <ChildComp/> </div> </template> <script> export default { components: { ChildComp: { methods: { heavy () { /* 耗时任务 */ } }, render (h) { return h('div', this.heavy()) } } } } </script>
但同时也不宜过度拆分组件,尤其是为了所谓组件抽象将一些不需要渲染的组件特意抽出来,组件实例消耗远大于纯dom节点。参考:https://vuejs.org/guide/best-practices/performance.html#avoid-unnecessary-component-abstractions
服务端渲染/静态网站生成:SSR/SSG
如果SPA应用有首屏渲染慢的问题,可以考虑SSR、SSG方案优化。参考SSR Guide
这道题考查大家对vue异步更新队列的理解,有一定深度,如果能够很好回答此题,对面试效果有极大帮助。
先看看官方定义
Vue.nextTick( [callback, context] )
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
// 修改数据 vm.msg = 'Hello' // DOM 还没有更新 Vue.nextTick(function () { // DOM 更新了 })