目录
一、组件通信方式
二、v-if和v-for
三、生命周期
1、描述
2、setup和created谁先执行
3、setup中为什么没有beforeCreate和created
四、双向绑定 v-model
1、定义
2、本质,原理
3、好处
五、如何扩展一个组件
1、mixins
缺点
2、slot插槽
3、extends
六、子组件可以直接改变父组件的数据吗
七、vue如何进行权限管理
八、对响应式理解
九、对虚拟 DOM 的理解
十、diff算法
十一、vue3新特性
十二、 怎么定义动态路由?怎么获取传过来的动态参数?
十三、从零开始写一个vue路由
十四、nextTick的使用和原理
十五、watch和computed的区别
十六、Vue 子组件和父组件创建和挂载顺序
十七、怎么缓存当前的组件?缓存后怎么更新?
十八、从0到1自己构架一个vue项目
十九、从 template 到 render 处理过程
二十、Vue实例挂载的过程中发生了什么
二十一、对vuex理解
1、定义
2、组成
3、缺点
4、action和mutation的区别是什么?
5、怎么监听vuex数据的变化
二十二、vue2中,Vue组件为什么只能有一个根元素,vue3中为什么可以有多个
二十三、怎么实现路由懒加载
二十四、vue3中,ref和reactive异同
二十五、watch和watchEffect异同
二十六、router-link和router-view是如何起作用的
二十七、vue-router实现路由跳转
二十八、History模式和Hash模式有何区别
二十九、页面刷新后vuex的state数据丢失怎么解决
三十、vue3的Composition API 与 vue2的Options API 有什么不同
三十一、vue-router中如何保护路由
三十二、对vue-loader的理解
三十三、如何自定义指令
三十四、$attrs和$listeners的使用场景
三十五、v-once的使用场景
三十六、v-memo的使用场景
三十七、什么是递归组件
三十八、什么是异步组件
三十九、如何处理vue项目中的错误
四十、vue渲染大量数据时应该怎么优化
四十一、SPA、SSR的区别是什么
四十二、存在首屏加载优化需求,SEO需求时,有什么解决方案
1、父 =》子
props:子组件用props接收父组件的数据
$attrs:
2、子 =》父
$emit:子组件通过$emit 传递(自定义事件传递),父组件通过@属性名称接收
3、兄弟
$parent
$root
4、祖孙
provide + inject
5、各种关系都通用
eventbus事件总线:new一个bus 中央总线的vue实例,发送端组件bus.$emit传递数据,bus.$on接收端组件接收数据
vuex
6、vue3废弃:$on,$children,$listeners
1、区别
v-if:数据的隐藏与显示
v-for:循环遍历渲染数据,key是唯一标识,为了比较两个节点是否相同,好处是可以更高效的更新虚拟DOM,避免使用数组索引作为key,如果不设置key,它的值就是undefined,则可能永远认为这是两个相同节点,只能去做更新操作,这造成了大量的dom更新操作,明显是不可取的
2、优先级
vue2:v-for > v-if
把它们放在一起,输出的渲染函数中可以看出会先执行循环再判断条件,哪怕我们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表,这会比较浪费
vue3:v-for < v-if
v-if执行时,它调用的变量还不存在,就会导致异常
beforeCreate:通常用于插件开发中执行一些初始化任务
created:组件初始化完毕,可以访问各种数据,获取接口数据等
mounted:dom已创建,可用于获取访问数据和dom元素;访问子组件等。
beforeUpdate:此时view
层还未更新,可用于获取更新前各种状态
updated:完成view
层的更新,更新后,所有状态已是最新
beforeunmount:实例被销毁前调用,可用于一些定时器或订阅的取消
unmounted:销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器
setup
setup是最早的。所以基本也不需要这两个钩子了(还是有不过是hooks)
vue中双向绑定是一个指令v-model
,可以绑定一个响应式数据到视图,同时视图中变化能改变该值
默认情况下相当于:value
和@input
转换为渲染函数之后,实际上还是是value属性的绑定以及input事件监听,事件回调函数中会做相应变量更新操作。编译器根据表单元素的不同会展开不同的DOM属性和事件对
可以减少大量繁琐的事件处理代码,提高开发效率。
4、自定义组件使用v-model
如果想要改变事件名或者属性名应该怎么做
5、v-model
和sync
修饰符有什么区别
// 复用代码:它是一个配置对象,选项和组件里面一样
const mymixin = {
methods: {
dosomething(){}
}
}
// 全局混入:将混入对象传入
Vue.mixin(mymixin)
// 局部混入:做数组项设置到mixins选项,仅作用于当前组件
const Comp = {
mixins: [mymixin]
}
不能明确判断来源且可能和当前组件内变量产生命名冲突。
解决方法:vue3中引入的composition api,利用独立出来的响应式模块可以很方便的编写独立逻辑并提供响应式的数据,然后在setup选项中组合使用,增强代码的可读性和维护性
如果要精确分发到不同位置可以使用具名插槽,如果要使用子组件中的数据可以使用作用域插槽
// 扩展对象
const myextends = {
methods: {
dosomething(){}
}
}
// 组件扩展:做数组项设置到extends选项,仅作用于当前组件
// 跟混入的不同是它只能扩展单个对象
// 另外如果和混入发生冲突,该选项优先级较高,优先起作用
const Comp = {
extends: myextends
}
不可以。
所有的 prop 都使得其父子之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这是组件化开发过程中的单项数据流原则。
这样是为了防止从子组件意外变更父级组件的状态。每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。
权限管理一般需求是两个:页面权限和按钮权限
1、页面权限
(1)前端方案
路由信息配置好,路由守卫要求用户登录,根据用户角色过滤出路由表
router.addRoutes动态添加路由
(2)后端方案
路由信息存入数据库,根据用户角色查找该用户能访问的所有路由,通过addRoutes动态添加路由信息
(3)优缺点
前端方案实现简单但不利于维护,有新的页面和角色需求就要修改前端代码重新打包部署
后端方案一劳永逸,通过专门的角色和权限管理页面,配置页面和按钮权限信息到数据库,应用每次登陆时获取的都是最新的路由信息
2、按钮权限
用户角色通过值传给v-permission指令,指令的mounted可以判断当前用户角色和按钮是否有交集,有就保留按钮,没有就移出按钮
1、定义
数据响应式就是能够使数据变化可以被检测并对这种变化做出响应的机制。通过数据驱动应用,数据变化,视图更新
2、好处
不用接触繁琐的DOM操作,从而大大提升开发效率,降低开发难度
3、如何实现
vue2:
不同数据类型采用的方法不一样
对象则采用Object.defineProperty()的方式定义数据拦截,当数据被访问或发生变化时,我们感知并作出响应;
数组则通过覆盖数组对象原型的7个变更方法,使这些方法可以额外的做更新通知,从而作出响应
对于es6中新产生的Map、Set这些数据结构不支持
vue3:
利用ES6的Proxy代理要响应化的数据
1、定义
虚拟的dom对象,是JavaScript对象,它是通过不同的属性去描述一个视图结构
2、好处
将真实元素节点抽象成 VNode,有效减少直接操作 dom 次数,从而提高程序性能
方便实现跨平台
3、如何生成
组件的模板 - template会被编译器 - compiler编译为渲染函数,在接下来的挂载(mount)过程中会调用render函数,这个函数返回的对象就是虚拟dom。但它们还不是真正的dom,所以会在后续的patch过程中进一步转化为dom。
4、虚拟DOM如何转换成真实DOM
挂载过程结束后,vue程序进入更新流程。如果某些响应式数据发生变化,将会引起组件重新render,此时就会生成新的vdom,和上一次的渲染结果diff就能得到变化的地方,从而转换为最小量的dom操作,高效更新视图。
递归过程,遵循深度优先、同层比较的策略
1、api层面Vue3新特性
Composition API、SFC Composition API语法糖(setup)、Teleport传送门、Fragments 片段、Emits选项、自定义渲染器、SFC CSS变量、Suspense
2、框架层面
1、为什么要有动态路由
将给定匹配模式的路由映射到同一个组件
2、路径参数 用冒号 :
表示。当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.params
的形式暴露出来
createRouter
函数,返回路由器实例,实例内部做几件事:
1、作用
等待下一次 DOM 更新刷新的工具方法,Vue有个异步更新策略,如果数据变化,Vue不会立刻更新DOM,而是开启一个队列,把组件更新函数保存在队列中,在同一事件循环中发生的所有数据变更会异步的批量更新。这一策略导致我们对数据的修改不会立刻体现在DOM上,此时如果想要获取更新后的DOM状态,就需要使用nextTick。
2、使用场景
3、原理
在Vue内部,nextTick之所以能够让我们看到DOM更新后的结果,是因为我们传入的callback会被添加到队列刷新函数(flushSchedulerQueue)的后面,这样等队列内部的更新函数都执行完毕,所有DOM操作也就结束了,callback自然能够获取到最新的DOM值
计算属性可以从组件数据派生出新数据,最常见的使用方式是设置一个函数,返回计算之后的结果,computed和methods的差异是它具备缓存性,如果依赖项不变时不会重新计算。
侦听器可以侦测某个响应式数据的变化并执行副作用,常见用法是传递一个函数,执行副作用,watch没有返回值,但可以执行异步操作等复杂逻辑。
父created、父beforemount、子created、子beforemount、子mounted、父mounted
1、缓存组件
keep-alive会缓存不活动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,防止重复渲染DOM
结合属性include和exclude可以明确指定缓存哪些组件或排除缓存指定组件
2、路由
vue2:keep-alive
包裹router-view
vue3:router-view
包裹keep-alive
3、原理
内部定义了一个map,缓存创建过的组件实例,它返回的渲染函数内部会查找内嵌的component组件对应组件的vnode,如果该组件在map中存在就直接返回它。由于component的is属性是个响应式数据,因此只要它变化,keep-alive的render函数就会重新执行
4、缓存后更新
(方式1)执行actived
钩子
(方式2)每次进入路由的时候,都会执行beforeRouteEnter
步骤:项目构建、引入必要插件、代码规范、提交规范、常用库和组件
在Vue中编译器会先对template进行解析,这一步称为parse,结束之后会得到一个JS对象,我们成为抽象语法树AST,然后是对AST进行深加工的转换过程,这一步成为transform,最后将前面得到的AST生成为JS代码,也就是render函数
app.mount()过程
1、初始化:创建组件实例、初始化组件状态,创建各种响应式数据
2、建立更新机制:立即执行一次组件更新函数,这会首次执行组件渲染函数并执行patch将前面获得vnode转换为dom;同时首次执行渲染函数会创建它内部响应式数据之间和组件更新函数之间的依赖关系,这使得以后数据变化时会执行对应的更新函数
采用集中式存储,管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
state、mutation、action
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation
Action
可以包含任意异步操作,但它不能修改状态, 需要提交mutation
才能变更状态
(方式1)通过watch选项或者watch方法监听状态,以字符串形式监听$store.state.xx
(方式2)使用vuex提供的API:store.subscribe(),回调函数接收mutation对象和state对象,这样可以进一步判断mutation.type是否是期待的那个,从而进一步做后续处理
vdom
是一颗单根树形结构,diff算法在遍历的时候从根节点开始遍历,它要求只有一个根节点。组件也会转换为一个vdom
,自然应该满足这个要求
vue3
中之所以可以写多个根节点,是因为引入了Fragment
的概念,这是一个抽象的节点,如果发现组件是多根的,就创建一个Fragment节点,把多个根节点作为它的children。将来patch的时候,如果发现是一个Fragment节点,则直接遍历children创建或更新
1、为什么要路由懒加载
当打包应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问时才加载对应组件,这样就会更加高效
2、实现
动态导入:给component
选项配置一个返回 Promise 组件的函数就可以定义懒加载路由,这样Vue Router 只会在第一次进入页面时才会获取这个函数,然后使用缓存数据。
结合注释() => import(/* webpackChunkName: "group-user" */ './UserDetails.vue')
可以做webpack代码分块
1、定义
ref
接收内部值返回响应式Ref
对象,通常用于处理单值响应式
reactive
返回响应式代理对象,用于处理对象类型响应式
2、用法
ref返回的响应式数据在JS中使用需要加上.value
才能访问其值
reactive不用加
3、数据类型
ref
可以接收对象或数组等非原始值,但内部依然是reactive
实现响应式
reactive
内部如果接收Ref
对象会自动脱ref
使用展开运算符(...)展开reactive
返回的响应式对象会使其失去响应性,可以结合toRefs()
将值转换为Ref
对象之后再展开
4、实现响应式的方式
ref
内部封装一个RefImpl
类,并设置存取器get value/set value
,拦截用户对值的访问,从而实现响应式
reactive
内部使用Proxy
代理传入对象并拦截该对象各种操作(trap),从而实现响应式
1、定义
watchEffect
传入的函数既是依赖收集的数据源,也是回调函数,立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数
watch
侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数。可以接收多种数据源,包括用于依赖收集的getter函数,因此它完全可以实现watchEffect的功能,同时由于可以指定getter函数,依赖可以控制的更精确,还能获取数据变化前后的值
2、是否会立即执行
watchEffect
在使用时,传入的函数会立刻执行一次。
watch
默认情况下并不会执行回调函数,除非我们手动设置immediate
选项
3、关系
watchEffect(fn) =
watch(fn,fn,{immediate:true})
router-link默认生成一个a标签,设置to属性定义跳转path。
router-view是要显示组件的占位组件,可以嵌套,对应路由配置的嵌套关系,配合name可以显示具名组件。
原理:
router-link组件内部根据custom属性判断如何渲染最终生成节点,内部提供导航方法navigate,用户点击之后实际调用的是该方法,此方法最终会修改响应式的路由变量,然后重新去routes匹配出数组结果,router-view则根据其所处深度deep在匹配数组结果中找到对应的路由并获取组件,最终将其渲染出来
声明式导航:router-link
编程式导航:router.push,router.replace
hash模式在地址栏显示的时候是已哈希的形式:#/xxx,这种方式使用和部署简单,但是不会被搜索引擎处理,seo有问题;
history模式则建议用在大部分web项目上,但是它要求应用在部署时做特殊配置,服务器需要做回退处理,否则会出现刷新页面404的问题。
底层实现上其实hash是一种特殊的history实现
vue-router4.x
const router = createRouter({
history: createWebHashHistory(), // hash模式
history: createWebHistory(), // history模式
})
1、产生原因
vuex只是在内存保存状态,刷新之后就会丢失,如果要持久化就要存起来
2、解决方法
(方法1)localStorage:提交mutation的时候同时存入localStorage,store中把值取出作为state的初始值即可
(方法2)插件:vuex-persist、vuex-persistedstate,内部的实现就是通过订阅mutation变化做统一处理,vuex提供的subscribe方法做一个统一的处理,通过插件的选项控制哪些需要持久化
Composition API
是一组API,包括:Reactivity API、生命周期钩子、依赖注入,使用户可以通过导入函数方式编写vue组件。对ts支持更友好
Options API
则通过声明组件选项的对象形式编写组件。
保护路由的方法叫做路由守卫,主要用来通过跳转或取消的方式守卫导航
路由守卫有三个级别:全局,路由独享,组件级
全局的router.beforeEach(),可以注册一个全局前置守卫,每次路由导航都会经过这个守卫,因此在其内部可以加入控制逻辑决定用户是否可以导航到目标路由;
在路由注册的时候可以加入单路由独享的守卫,例如beforeEnter,守卫只在进入路由时触发,因此只会影响这个路由,控制更精确;
还可以为路由组件添加守卫配置,例如beforeRouteEnter,会在渲染该组件的对应路由被验证前调用,控制的范围更精确了。
用户的任何导航行为都会走navigate方法,内部有个guards队列按顺序执行用户注册的守卫钩子函数,如果没有通过验证逻辑则会取消原有的导航。
用于处理单文件组件的webpack loader,可以在项目中编写SFC格式的Vue组件。
webpack打包时,会以loader的方式调用vue-loader。
vue-loader
被执行时,它会对SFC中的每个语言块用单独的loader链处理。最后将这些单独的块装配成最终的组件模块
1、定义
有两种方式:对象和函数形式,前者类似组件定义,有各种生命周期;后者只会在mounted和updated时执行
2、注册
app.directive()全局注册,使用{directives:{xxx}}局部注册
3、使用
v-注册名
4、自定义指令后发生什么
编译后的自定义指令会被withDirective函数装饰,进一步处理生成的vnode,添加到特定属性中
5、在v3.2之后,可以在setup中以一个小写v开头方便的定义自定义指令
50+Vue经典面试题源码级详解(34) - 掘金 (juejin.cn)
作用:仅渲染指定组件或元素一次,并跳过未来对其更新
作用:有条件缓存部分模板并控制它们的更新
1、定义
某个组件通过组件名称引用它自己
2、原理
递归组件查找时会传递一个布尔值给resolveComponent
,这样实际获取的组件就是当前组件本身
1、定义
在大型应用中,我们需要分割应用为更小的块,并且在需要组件时再加载它们,这时就需要用到异步组件
2、使用
给defineAsyncComponent指定一个loader函数,结合ES模块动态导入函数import可以快速实现
1、对于接口异常
可能是请求失败,也可能是请求获得了服务器响应,但是返回的是错误状态
封装Axios,在拦截器中统一处理整个应用中请求的错误
2、对于代码逻辑错误
使用全局错误处理函数app.config.errorHandler
收集错误
import { createApp } from 'vue'
const app = createApp(...)
app.config.errorHandler = (err, instance, info) => {
// report error to tracking services
}
1、采用懒加载方式,在用户需要的时候再加载数据
2、采取分页的方式获取,避免渲染大量数据
3、通过v-memo可以缓存结果,结合v-for
使用,避免数据变化时不必要的VNode创建
SPA(Single Page Application)即单页面应用。一般也称为 客户端渲染(Client Side Render), 简称 CSR。SSR(Server Side Render)即 服务端渲染。一般也称为 多页面应用(Mulpile Page Application),简称 MPA。
SPA应用只会首次请求html文件,后续只需要请求JSON数据即可,因此用户体验更好,节约流量,服务端压力也较小。但是首屏加载的时间会变长,而且SEO不友好。为了解决以上缺点,就有了SSR方案,由于HTML内容在服务器一次性生成出来,首屏加载快,搜索引擎也可以很方便的抓取页面信息。但同时SSR方案也会有性能,开发受限等问题。
1、SSR
2、预渲染
3、nuxt.js/next.js中的SSG静态网站生成方案