vue
是组件化开发框架,所以对于vue应用来说组件间放入的数据通信非常重要。此题主要考察大家vue基本功,对于vue基础api熟练度
。另外一些边界知识如provide/inject/$attrs
则体现了面试者的知识广度。
组件传参的各种方式
注意vue3废弃的几个Api
- $children
- $listeners
- v-on.native
父子组件
– props
/ $emit
/ $parent
/ ref
/ $attrs
兄弟组件
– $parent
/ $root
/ eventbus
/ vuex
跨层级关系
– eventbus
/ vuex
/ provide + inject
此题考查常识,文档中曾有详细说明 v2 | v3;也是一个很好的实践题目,经常会遇到,能够看出面试者api熟悉程度和应用能力。
vue2
中,v-for的优先级是高于v-if的,把他们放在一起,输出的渲染函数中可以看出会先执行循环再执行判断,哪怕我们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表,这会比较浪费;另外需要注意的是在vue3
中则完全相反,v-if的优先级高于v-for,所以v-if执行的时候,它调用的变量还不存在,就会导致异常v-for="item in list" v-if="item.isShow"
),此时定义一个计算属性或者直接v-for时过滤改列表(比如:v-for="item in list.filter(codin => codin.age > 0)"
)v-for="item in list" v-if="isShow"
),此时把v-if移动至容器元素上(比如 ul
、ol
)或者外面包一层 template
即可明确指出
永远不要把 v-if
和 v-for
同时用在同一个元素上,显然这是一个注意事项必问题目,考查vue基础知识
生命周期Vue2 | 生命周期Vue3 | 描述 |
---|---|---|
beforeCreat | beforeCreate | 组件实例被创建之初 |
created | created | 组件实例已经完全创建 |
beforeMount | beforeMount | 组件挂载之前 |
mounted | mounted | 组件挂载到实例上去之后 |
beforeUpdate | beforeUpdate | 组件数据发生变化,更新之前 |
updated | updated | 数据更新之后 |
beforeDestory | beforeUnmount | 组件实例销毁/卸载之前调用 |
destoryed | unmounted | 组件实例销毁/卸载之后调用 |
生命周期Vue2 | 生命周期Vue3 | 描述 |
---|---|---|
activated | activated | keep-alive缓存的组件激活时调用 |
deactivated | deactivated | keep-alive 缓存的组件停用时调用 |
errorCaptured | errorCaptured | 捕获一个来自子孙组件的错误时调用 |
- | renderTracked | 调试钩子(开发模式可用),响应式依赖被收集时调用(在一个响应式依赖被组件的渲染作用追踪后调用) |
- | renderTriggered | 调试钩子(开发模式可用),响应式依赖被触发时调用(在一个响应式依赖被组件触发了重新渲染之后调用) |
- | servePrefetch | ssr only(服务端渲染中执行),组件实例在服务器上被渲染时调用 |
dom
已创建,可用于获取访问数据和dom元素,访问子组件等view
层还未更新,可用于获取更新前各种状态view
层的更新,更新后,所有状态已是最新beforeUnmount
:实例被销毁之前调用,可用于一些定时器或订阅的取消unmounted
:销毁一个实例。可清理他与其他实例的连接,解绑他的全部指令及事件监听器可能的追问:(这两个问题学习:https://juejin.cn/post/7004449155883991054)
1、setup和created谁先执行?
2、setup中为什么没有beforeCreate和created?
双向绑定是vue的特色之一,是在开发中必然会用到的知识点,然而此题还问了实现原理,升级为深度考查。
v-model
是语法糖,默认情况下相当于 :value 和 @input。使用v-model可以减少大量繁琐的事件处理代码,提高开发效率。
的方式将XXX的值绑定到表单元素value上,对于checkbox,可以使用true-value
和false-value
指定特殊的值,对于radio可以使用value指定特殊的值;对于select可以通过options
元素的value设置特殊的值;还可以结合.lazy
、.number
、.trim
对v-model的行为做进一步限定;v-model用在自定义组件上又会有很大不同,vue3中它类似于sync
修饰符,最终展开的结果是modelValue
属性和update:modelValue
事件;vue3中我们甚至可以用参数形式制定多个不同的绑定。例如v-model:foo
和v-model:bar
,非常强大v-model
是一个指令,它的神奇魔法实际上是vue的编译器完成的。我做过测试,包含v-model的模板,转换为渲染函数之后,实际上还是是value属性的绑定以及input事件监听,事件回调函数中会做相应变量更新操作。编译器根据表单元素的不同会展开不同的DOM属性和事件对,比如text类型的input和textarea会展开为value和input事件;checkbox和radio类型的input会展开为checked和change事件;select用value作为属性,用change作为事件。可能的追问:
1、v-model
和sync
修饰符有什么区别
2、自定义组件使用v-model
如果想要改变事件名或者属性名该怎么做
这是一道必问题,但是能回答到位的比较少。如果只是看看一些网文,通常没什么底气,经不住面试官推敲,但像我们这样既看过源码还造过轮子的,回答这个问题就会比较有底气。
Object.defineProperty()
的方式定义数据拦截,当数据被访问或发生变化时,我们感知并作出响应;如果是数组则通过覆盖数组对象原型的7个变更方法,使这些方法可以额外的做更新通知,从而作出响应。这种机制很好的解决了数据响应化的问题,但在实际使用中也存在一些缺点:比如初始化时的递归遍历会造成性能损失;新增或删除属性时需要用户使用Vue.set/delete这样特殊的api才能生效;对于es6中新产生的Map、Set这些数据结构不支持等问题。此题属于实践题,考查大家对vue常用api使用熟练度,答题时不仅要列出这些解决方案,同时最好说出他们异同。
- 逻辑扩展有:
mixins
、extents
、composition api
;- 内容扩展有:
slots
;
vue3
中新引入的composition api
带来的变化mixins
、slots
、extends
等mixins
是分发Vue组件中可复用功能的非常灵活的方式。混入对象可以包含任意对象选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。// 复用代码:它是一个配置对象,选项和组件里面一样
const mymixin = {
methods: {
dosomething(){}
}
}
// 全局混入:将混入对象传入
Vue.mixin(mymixin)
// 局部混入:做数组项设置到mixins选项,仅作用于当前组件
const Comp = {
mixins: [mymixin]
}
slots
主要用于vue组件内的内容分发,也可以用于组件扩展。<div>
<slot>这个内容会被父组件传递的内容替换slot>
div>
<div>
<Child>来自老爹的内容Child>
div>
如果要精确分发到不同位置可以使用具名插槽,如果要使用子组件中的数据可以使用作用于插槽。
extends
,也可以起到扩展组件的目的// 扩展对象
const myextends = {
methods: {
dosomething(){}
}
}
// 组件扩展:做数组项设置到extends选项,仅作用于当前组件
// 跟混入的不同是它只能扩展单个对象
// 另外如果和混入发生冲突,该选项优先级较高,优先起作用
const Comp = {
extends: myextends
}
composition api
,可以很好地解决这些问题,利用独立出来的响应式模式可以很方便的编写独立逻辑并提供响应式数据,然后在 setup选项中组合使用,提高代码的可读性和可维护性。例如:// 复用逻辑1
function useXX() {}
// 复用逻辑2
function useYY() {}
// 逻辑组合
const Comp = {
setup() {
const {xx} = useXX()
const {yy} = useYY()
return {xx, yy}
}
}
可能的追问:
Vue.entend方法你用过吗?你能用它来做组件扩展吗?
这是一个实践知识点,组件开发过程中有个单向数据流原则,不在子组件中修改父组件数据是个常识问题。
参考文档:https://vuejs.org/guide/components/props.html#one-way-data-flow
const props = defineProps(['foo'])
// ❌ 下面行为会被警告, props是只读的!
props.foo = 'bar'
const props = defineProps(['foo'])
const value = ref(props.foo)
// 用value转赋值之后,随意改动value的值,浏览器也不会发出警告
const props = defineProps(['size'])
// prop变化,计算属性自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
这是一个实践知识点,组件开发过程中有个单向数据流原则,不在子组件中修改父组件数据是个常识问题。
参考文档:https://vuejs.org/guide/components/props.html#one-way-data-flow
const props = defineProps(['foo'])
// ❌ 下面行为会被警告, props是只读的!
props.foo = 'bar'
const props = defineProps(['foo'])
const value = ref(props.foo)
// 用value转赋值之后,随意改动value的值,浏览器也不会发出警告
const props = defineProps(['size'])
// prop变化,计算属性自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
综合实践题目,实际开发中经常需要面临权限管理的需求,考察实际应用能力。
权限管理一般需求是两个:页面权限 和 按钮/数据权限,从这两个方面论述即可。
asyncRoutes
数组,需要认证的页面在其路由的meta中添加一个roles
字段,等获取用户角色之后取两者的交集,若结果不为空则说明可以访问。此过滤过程结束,剩下的路由就是该用户能访问的页面,最后通过router.addRoutes(accessRoutes)
方式动态添加路由即可。addRoutes
动态添加路由信息v-permission
,将按钮要求角色通过值传给v-permission指令,在指令的moutned
钩子中可以判断当前用户角色和按钮是否存在交集,有则保留按钮,无则移除按钮。
- 纯前端方案的优点是实现简单,不需要额外权限管理页面,但是维护起来问题比较大,有新的页面和角色需求就要修改前端代码重新打包部署;
- 服务端方案就不存在这个问题,通过专门的角色和权限管理页面,配置页面和按钮权限信息到数据库,应用每次登陆时获取的都是最新的路由信息,可谓一劳永逸!
Tabs
这类组件能不能使用v-permission
指令实现按钮权限控制?<el-tabs>
<el-tab-pane label="⽤户管理" name="first">⽤户管理el-tab-pane>
<el-tab-pane label="⻆⾊管理" name="third">⻆⾊管理el-tab-pane>
el-tabs>
// 前端组件名和组件映射表
const map = {
//xx: require('@/views/xx.vue').default // 同步的⽅式
xx: () => import('@/views/xx.vue') // 异步的⽅式
}
// 服务端返回的asyncRoutes
const asyncRoutes = [
{ path: '/xx', component: 'xx',... }
]
// 遍历asyncRoutes,将component替换为map[component]
function mapComponent(asyncRoutes) {
asyncRoutes.forEach(route => {
route.component = map[route.component];
if(route.children) {
route.children.map(child => mapComponent(child))
}
})
}
mapComponent(asyncRoutes)