前端基础面试题·第四篇——Vue(其三)
1.在Vue组件中写name选项有什么作用?
Vue中组件定义式可以为其添加一个name属性,这个属性是组件的别名。
const { createApp } = Vue;
// 定义组件
const MyComponent = {
name: "MyComponent", // 设置组件名称
// 其他组件选项...
};
// 创建Vue应用实例
const app = createApp({});
// 全局注册组件
app.component(MyComponent.name, MyComponent);
app.mount('#app');
1.提供调试帮助
当时组件被渲染时,Vue DevTools 会显示组件的名称,方便开发者调试,增强调试体验。
2.递归组件
当组件需要在内部调用渲染自己本身时,需要通过name属性来指定组件名称,否则Vue会报错。
const MyComponent = {
name: "MyComponent",
template: `<div>
<p>这是一个递归组件</p>
<MyComponent />
</div>`,
};
3.全局注册组件
给组件命名后,我们可以通过全局注册,在全应用范围内使用该组件。方便了组件的使用,提高了组件复用性。
4.keep-alive
使用keep-alive包裹动态组件时,可以通过include和exclude属性来指定哪些组件需要缓存,哪些组件不需要缓存。这时,组件的name属性就派上用场了。
<keep-alive include="MyComponent">
<component :is="currentComponent"></component>
</keep-alive>
5.动态组件
动态组件的is属性可以指定组件名称,这时组件的name属性就派上用场了。
<component :is="currentComponent"></component>
2. VueRouter 路由模式
1.hash 模式
hash模式是通过createHashHistory()方法创建的,hash模式路由在地址栏中会显示一个#号,由于这部分url从未被发送到服务器,所以hash模式不需要后端配置。但是这SEO优化方面存在缺陷。
2.memory 模式
memory模式是通过createMemoryHistory()方法创建的,这种路由模式假定自己不处于浏览器模式,因此不会自动触发初始导航。这种模式更加适用于node和ssr,并且需要通过调用app.usr(router)之后手动push到初始导航。
3.history 模式
history模式通过createWebHistory()方法创建的,这个路由模式需要在服务器上专门配置一个回退路由,否则用户刷新页面会返回404。
注意: 一旦在服务端配置回退路由之后,服务端不会再返回404错误页面了,因此如果需要404页面我们需要在前端路由中专门配置。
扩展: 项目上线history路由会遇到的问题
上面提到的history路由模式地址栏中没有#号,视觉效果非常干净整洁。当我们将项目上线到服务器上后,通过域名访问项目,第一次项目访问会返回需要的页面结果,但当再次刷新或者切换一个页面之后在刷新就会出现404错误。
- 原因: 这个原因就是后端服务只配置了一个index.html页面,也就是只有当你访问对应的域名时会返回一个唯一的index.html页面,然后浏览器通过index文件解析下载对应的资源,等会到js生效之后前端的路由才会被激活,我们才可以切换显示页面。但当第二次或者之后刷新时,因为前端路由已经被激活,前端现在的导航栏路径肯定不再是原先的域名了,再次属性浏览器就会将当前的导航栏路由发送给服务器来获取对应的页面,但由于此时的路径是前端路由生效后的路径,服务器没有专门为这个路由做配置,因此就会返回404错误。
- 解决方案: 解决方式就是当再次刷新时,我们想办法让前端的路由再次生效。所以一般来说会在后端配置一个回退路由,当匹配不到路径时将index.html页面返回,一旦index.html返回给前端,前端路由就会再次被激活,此时路由会按照导航栏路径再次匹配,最终就会匹配到需要的页面。
- 存在问题: 这样的方式会让无论导航栏是什么样服务器总是返回首页,如果我们能访问的页面不存在,我们是希望看到404页面的,因此就需要专门在前端路由中配置一下440页面的路由。
- 哈希路由为什么没有这个问题: 哈希路由模式在地址栏在域名,每一级路由之间都会有一个#号,浏览器在向服务器发送请求时,会自动截取第一个#号之前的部分作为路径,因此无论在哪个页面刷新,向到服务器请求的页面始终是首页,回来之后前端路由始终会被激活,最终就会匹配到需要的页面。因此就不会存在上面的问题。
3. vue-router原理
1. 监听URL变化
vue-router是通过监听浏览器地址栏的URL变化来实现页面切换的。具体实现方式是使用HTML5 History API中的pushState和replaceState方法,以及window.onpopstate事件来监听URL的变化。
2. Vue响应式系统
Vue Router 利用Vue的响应式系统来更新视图。当路由变化时,route对象(代表当前路由的状态)会被更新,这会触发依赖于route的组件重新渲染。
3. 路由匹配
Vue Router使用路由配置来匹配URL,并根据匹配结果渲染相应的组件。当用户访问某个URL时,Vue Router会遍历所有路由配置,找到第一个与当前URL相匹配的路由,并渲染该路由对应的组件。
4.动态组件 router-view:
Vue Router 使用一个名为 router-view 的动态组件作为路由出口。当路由变化时,Vue Router会根据匹配的路由记录,动态地向 router-view插入相应的组件。当路由变化,router-view中的组件将会被销毁并重新渲染。
5.路由守卫
Vue Router提供了路由守卫来控制路由的访问权限和导航行为。路由守卫分为全局守卫、路由守卫和组件内守卫三种类型。
Vue Router 提供了导航守卫(navigation guards),这些守卫可以在路由跳转前后以及在跳转被确认前进行拦截。这些守卫可以用来处理权限检查、数据预加载等逻辑,并且可以取消导航。
拓展-路由守卫
- 全局前置守卫:全局前置守卫是路由跳转前的拦截,在任何路由跳转前都会触发。
- 全局解析守卫:全局解析守卫是路由跳转前和组件渲染前触发,在任何路由跳转前都会触发。
- 全局后置钩子:全局后置钩子是在路由跳转完成后触发,在任何路由跳转完成后都会触发。
- 路由独享守卫:路由独享守卫是在某个路由配置上直接定义的前置守卫,它只能对当前路由进行控制。
- 组件内守卫:组件内守卫是在路由组件内部直接定义的守卫,它只能对当前路由组件进行控制。
4. VueRouter 路由守卫
1.全局前置守卫
通过调用router.beforeEach 为全局路由注册一个前置路由守卫。
router.beforeEach((to,form) => {
return false // 阻止路由跳转
})
- 该函数接收一个处理函数作为参数,处理函数会被挂载两个参数,to和form。
- to,路由跳转的目标路由对象,form路由跳转的来源路由对象。
- 该函数可以返回false,阻止路由跳转。或者返回一个路由地址(router.push接受的参数类型均可返回),进行路由跳转。
- 如果在该函数中抛出错误,会取消路由跳转并调用router.onError注册的全局钩子。就是触发router的生命周期钩子函数。
- 如果函数返回true或没有返回值,则路由跳转成功。
注意: 该函数还会被注入第三个参数(旧版本),第三个参数是next函数。(新版本已经移除,通过RFC讨论,但是本身仍然被支持),一但接受这个参数,就需要在前置路由守卫中必须调用且必须保证该函数在真个守卫中只能调用一次。
该函数调用传入参数表示放行路由,如果传递参数表示拦截路由并重定向到执行的路由。接受的参数与router.push接受的参数相同。
2.全局解析守卫
通过router.beforeResolve 注册全局解析守卫。该守卫与全局前置守卫类似,区别在于该守卫会在所有组件内守卫和异步路由组件被解析之后,也就是在路由跳转成功之后调用。这就很适合在跳转之后做一些事情。比如说页面跳转之后提示用户开启某系权限,或者判断用户是否登录等。
3.全局后置守卫
通过router.afterEach 注册全局后置钩子。该钩子节后参数与全局前置守卫相同,但是调用时机是在路由跳转完成之后调用。但缺少了next参数,无法控制路由跳转。它的第三个参数为failures ,这个参数是一个数组,包含所有路由跳转的失败的跳转对象。并且包含失败原因,因此我们可以在该钩子中做路由跳转失败的提示或者特殊处理。
router.afterEach((to, from, failure) => {
if (!failure) sendToAnalytics(to.fullPath)
})
全局注入
从Vue3.3开始可以在路由守卫中使用inject 函数注入全局提供的属性。
// main.ts
const app = createApp(App)
app.provide('global', 'hello injections')
// router.ts or main.ts
router.beforeEach((to, from) => {
const global = inject('global') // 'hello injections'
// a pinia store
const userStore = useAuthStore()
// ...
})
4.路由独享守卫
路由独享守卫是对于某一个特定的路由来说的,它和全局守卫的区别在于,路由独享守卫是写在路由配置文件中的。直接在路由配置中通过beforeEnter 属性来配置
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
- 该守卫的参数与全局前置守卫相同。没有next参数,无法控制路由跳转。
同时该守卫只会在路由匹配时调用,在query,params ,hash等改变是不会重复触发的。 - 该守卫如果用在嵌套路由上,再父;路由和子路由都可以配置 ,父路由的守卫只会在其他的父级路由切换到现在的路由时调用。在子路由之间跳转不会触发父路由的守卫,只会触发子路由的守卫。
const routes = [
{
path: '/user',
beforeEnter() {
// ...
},
children: [
{ path: 'list', component: UserList },
{ path: 'details', component: UserDetails },
],
},
]
示例中的 beforeEnter 在 /user/list 和 /user/details 之间移动时不会被调用,因为它们共享相同的父级路由。如果我们直接将 beforeEnter 守卫放在 details 路由上,那么在这两个路由之间移动时就会被调用。
5.组件内守卫
组件内守卫是写在组件内的,通过组件的声明周期钩子函数来实现的。在组件内通过beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave来实现。
<script>
export default {
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
</script>
- beforeRouteEnter 无法直接访问组件实例,但可以通过第三个参数next函数来间接访问,该函数在路由被完全挂在后调用,并且注入组件实例。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
- beforeRouteLeave 该函数还可以通过返回一个false来取消路由跳转。主要用于离开组件时做一些确认工作。
注意: beforeRouteUpdate 和beforeRouteEnter 在组合式API中有对应的函数 onBeforeRouteUpdate,onBeforeRouteUpdate