Vue单向数据流
- 什么是vue单向数据流 vue单向数据流指的是数据总是从父组件传递到子组件。子组件没有权限修改父组件传递来的数据。只能请求父组件对原始数据进行修改。
- 为什么?优点? vue单向数据流可以防止子组件意外改变父组件的数据,导致组件间的数据流向难以理解和维护。
- 如何才能修改父组件传递来的数据呢
- 定义一个新的变量,保存父组件props传递来的数据,然后对这个新的变量进行修改。
- 定义一个计算属性,通过父组件传递来的原始值计算出新的值进行使用
- $emit()发出事件,让父组件修改数据。父组件监听到这个事件,由父组件修改数据。
Vue组件通信方式
- 按照组件关系描述使用场景
- 父子组件通信:父子props、子->父parent中访问父组件、父组件通过ref访问子组件、爷孙之间$attrs
- 兄弟组件:通过共同的关系人root、eventbus、vuex
- 跨层级关系:eventbus、vuex、provide、inject
- vue3中变化
- listeners,$on被废除了,eventbus用起来不方便,推荐引入第三方api代替eventbus
- provide和inject
- provide和inject都是成对出现的
- 用父组件向子孙组件跨层级的传递数据
- 使用方法:provide中返回要传给下一级的数据,inject在下一级的组件中注入数据
Vuex页面刷新导致数据丢失
- 原因 因为vuex保存的数据使非持久化的。
- 解决
- 存储在本地存储中,local Storage中保存的数据是持久化的,关闭页面后仍然存在。
- 使用第三方插件,vuex-persist。
- 场景 需要保存用户的登陆状态,刷新页面以后登陆状态仍然存在。因此需要将token信息保存在本地存储中。
Computed和watch的区别
- 两者的定义
-
Computed:计算属性。是从已有的属性中经过计算生成新的属性。
-
Watch:监视属性。是用于监视已有属性的变化,检测到变化以后执行回调函数。
-
- 不同点
- 缓存机制:computed存在缓存机制。当初次获取数据以后,如果再次获取相同的数据就会从缓存中获取。只在初次读取数据和依赖的数据发生改变时读取数据。儿watch和(method)都不存在缓存机制。
- 异步任务:computed是同步读取数据,不能开启异步任务。而watch可以开启异步任务,执行更加复杂的业务逻辑。
- computed是计算出新的属性,而watch监视的数据必须存在。
- 使用场景:computed用于简化模板中的复杂表达式。(官方不建议模板中写太长的表达式)而watch用于状态变化后进行一些异步操作或者更加复杂的业务逻辑。
- 使用细节
-
computed中可以设置get()和set(),时数据变得可读又可写。
-
watch中可以设置一些其他配置,比如immediate设置立即监听,设置deep实现深度监听。
-
computed实现原理
- 思路
- 计算的属性要建立和依赖的属性之间的联系
- 依赖的属性改变后,要通知计算属性重新计算
- 具体实现
- 初始化data->通过Object.defineProperty把这些属性全部转换为getter和setter
- 初始化computed,遍历computed中的每一个属性,给每个属性添加一个watcher,提供的函数将作为计算属性的getter
- 当首次获取计算属性的值,通过dep进行依赖收集,会建立属性和计算属性之间的依赖关系。这样当计算属性依赖的属性发生变化,会根据依赖关系,触发计算属性的重新计算。
- 这时会进行一个脏值判断,如果没有被执行过才进行计算,也是计算属性缓存的原理。
v-if与v-for的优先级
- vue2.0中
- v-for的优先级高于v-if,先循环渲染列表再执行判断。
- 因此无论如何都会循环遍历了整个数据,然后对每一条都执行if判断。因此尽量不同时使用,会造成性能上的浪费。
- 解决方案是在循环列表的外层套一个容器,在外层容器中使用v-if。
- vue3.0中
- 做了改变,v-if的优先级高于v-for。
- 因此不能同时使用,会报错。因为在列表循环渲染之前,没有这个节点无法执行判断。
vue中为什么使用key
- 为什么?优点
- vue中使用key可以更高效的更新虚拟dom。因为key可以给虚拟节点添加上唯一标识id,这样diff算法在进行节点间的比较会更加高效。(标签相同、key相同的节点是可以直接复用,不需要更新渲染)
- 必要性:如果不用key会发生什么
- 如果不加key,不能根据key直接找到相应的节点,那么相同的节点会被不断更新导致效率低下。
- 为什么不可以使用index作为key
- 如果用index作为key,如果不按照index的顺序添加节点,那么添加节点后会导致顺序错乱,也会导致更多次的比较,效率也会更低。
Vue的生命周期方法?一般在哪一步发送请求?
- 生命周期方法
- beforeCreate 创建实例前,data未加载
- created 实例创建完成,data加载完成,属性方法watch都有,但是没有dom
- beforeMount 虚拟dom创建完成,渲染前最后一次修改数据
- mounted 真实dom挂载完成,数据完成双向绑定,页面渲染完成
- beforeUpdate 数据更新前,此时data已经被修改,但是dom还未修改
- updated 数据更新完成,dom已经修改完成
- beforeDestory 实例销毁前,实例仍然可用,可以清除定时器
- destoryed 实例销毁后,所有属性解绑,事件监听移除,所有子实例销毁
- actived keep-alive 组件被激活的时候调用,可以在这一步获取被缓存的组件数据
- deactived keep-alive组件被销毁时调用
- 在哪一步发起异步请求?
- 可以在created、beforeMount、mounted中发送异步请求,这时data已经被创建,可以对服务端返回的数据进行赋值。
- 如果不用依赖dom,推荐在created中发送请求。因为越早获取数据,页面会更快地加载。并且ssr服务端不支持beforeMount和mounted钩子,在created中发送请求有助于保持一致性,有利于进一步的ssr优化。
Vue中父子组件的创建、挂载、更新、销毁流程
- 创建和挂载 父组件先创建,后挂载,子组件后创建,先挂载。
父BeforeCreate&&Created--->父BeforeMount--->子BeforeCreate&&Created--->子BeforeMount--->子Mounted--->父Mounted
- 更新 父BeforeUpdate--->子BeforeUpdate--->子Updated--->父Updated
- 销毁 父BeforeDestroy--->子BeforeDestroy--->子Destroyed--->父Destroyed
Vue中自定义指令的生命周期钩子
- 自定义指令
- vue有一些默认指令比如v-model,v-for,v-show等等,同时也允许用户注册自定义指令扩展vue,封装一些可复用的操作。
- 使用directives配置,v-xxx
- vue2.0自定义指令中的生命周期
- bind(){} 在指令绑定时调用
- inserted(){} 在绑定的元素被插入页面时调用
- update(){} 在更新时调用。
- componentUpdate(){} 被绑定的元素在模板完成一次更新时调用
- unbind(){} 只调用一次,指令与元素解绑的时候调用
- vue3.0中自定义指令的生命周期钩子
- 3.0中自定义指令的生命周期钩子发生了比较大的变化,更加的语义化了,名称和组件的生命周期钩子一致,更加方便记忆。
- created
- beforeMount:指令第一次绑定在元素上执行,挂载在父组件之前执行
- Mounted:被插入node之后
- beforeUpdate:更新之前调用
- updated:组建的vnode和子组件vnode更新之后调用
- beforeUnmounted:卸载之前
- unmounted:解绑之后
- 如何使用?
- 在directives中配置,可以是函数或者对象形式
- 对象形式中,可以使用各种生命周期钩子,比如bind(),inserted(),update()。函数形式的只会在bind()和update()中执行。
- 可以全局配置Vue.directive(),也可以在组件中引入然后局部注册只在单个组件中生效。
- 配置完成使用时前面加上v-即可
- 项目中使用的一些自定义指令
- 复制粘贴v-copy
- 长按v-longpress
- 防抖v-bebounce
- 图片懒加载v-lazy
- 按钮权限
- 页面水印
- 拖拽指令v-drag
- 手写一个自定义指令fbind实现页面刷新获取input的焦点
directives: {
fbind:{
//每一个生命周期函数包含两个参数,一个用于获取绑定的元素,一个是绑定的值
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时被调用
inserted(element,binding){
element.focus()
},
//指令所在模板被重新解析时调用
update(element,binding){
element.value = binding.value
element.focus()
}
}
},
- 封装过哪些自定义指令?
- 输入框防抖
//封装一个点击事件的节流函数
throttle:{
bind(el,binding){
let wait = binding.value;
if(!wait){
wait = 2000;
}
let timer;
el.addEventListener('click',e=>{
if(!timer){
timer = setTimeout(() => {
timer = null
}, wait);
}else{
event && event.stopImmediatePropagation();
}
},true)
}
}
keep-alive--如何缓存当前的组件,缓存以后怎么更新。
-
keep-alive的作用
-
keep-alive是用于缓存组件的,是vue的内置组件。
-
用
<keep-alive>包裹的组件会缓存不活动的组件实例,路由切换时不会被重新创建或销毁。组件在切换的过程中状态仍保留在内存中,直接从内存中获取组件,不会重新创建组件和渲染。
-
-
使用场景
- 切换路由,再次进入路由时不需要发送请求获取数据再执行一次生命周期,而是直接走缓存。组件切换的时候只执行actived生命周期函数。
- 比如从首页到列表页再到商品详情页,再返回到列表页,就需要对列表页进行缓存,这样返回列表页时仍然停留在上次的浏览位置,而不需要重新请求页面走一遍生命周期
- 再比如一些表单元素,在用户填写完一部分表单内容,然后切换到其他路由时,如果不使用keepalive缓存表单的组件,那么用户的输入内容将会被清空。如果使用keep-alive包裹组件,那么切换到其他页面中会保留用户的输入内容,用户体验会更好。
-
使用 通过include指定要缓存组件的组件名称(正则匹配)。,exclude排除不缓存的,max指定最多缓存的。
-
原理 在 created 函数调用时将需要缓存的 VNode 节点保存在 this.cache 中/在 render(页面渲染) 时,如果 VNode 的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从 this.cache 中取出之前缓存的 VNode 实例进行渲染。
-
生命周期
-
actived(){}组件被激活时调用。
-
deactived(){}组件失活时调用,页面被切换走。
-
组件被缓存以后如何获取数据,更新
- 在beforeRouteEnter中获取数据。有vue-router的项目中,每次进入路由会执行
- actived在keep-alive缓存的组件被激活时执行。
vue-router的实现原理?
-
vue-router:通过解析url实现不同页面间的跳转。根据不同的url跳转到不同的页面,实现不需要重新请求页面,只更新视图。
-
vue-router的两种模式
- hash:通过window.onhashchange方法监听hash值的变化。
- history:window.onpopState()方法监听浏览器的前进后退。
-
具体实现
- 首先定义一个createRouter函数,返回一个路由实例router,实例中保存用户传入的配置项hash模式或者history模式,监听hash或者popState事件,根据path匹配到对应的路由,在router-view中render这个组件。
- 路由实例是一个Vue插件,实现install方法,
- 实现两个全局组件router-link和router-view,分别实现页面跳转和内容显示。router-link是通过创建一个a标签实现。router-view根据对应的url找到对应的组件并且render出来。
- 定义两个全局变量,router,组件可以访问当前的路由和路由实例。
hash和history两种模式的区别
- hash
- 虽然出现在url中但是不会被包含在http请求中,因此改变hash不会重新加载页面。hash的改变记录在window.history中。
- 不利于SEO优化,
- hash通过window.onhashchange()监听hash的改变。(通过location.hash改变哈希值)
- 在#后面不够美观,通过location.hash获取
- 若地址通过第三方app分享,如果app校验严格,会被标记为不合法。
- 兼容性好
- history
- 通过history.pushState()和history.replaceState()方法改变url。通过window.onPopState()方法监听history.back()和history.go()(用户的前进和后退事件)
- SEO优化:可以额外添加title属性
- url必须与后端一致,否则会跳转404
如何定义动态路由?怎么获取传过来的动态参数??
- 什么是动态路由? url路径中含有动态参数的路由。vue-router可以通过模式匹配匹配到相应的路由。
- 什么时候使用动态路由,如何定义?
- 根据不同的用户展示不同的userid,不同的商品id。
- 路由跳转的时候传递params参数。在路由配置的时候中使用一个动态的字段:id,这点:id就是路径参数都映射到一个相同的组件中。
- 如何获取参数??
- this.$route.params获取到params,进行动态展示。
- vue3中使用的是useRoute和useRouter分别获取全局路由和局部路由信息,方便在setup中获取路由相关的信息。
- 其他
- 定义动态路由时可以定义多个参数
动态的添加路由?
- 什么场景需要动态的添加路由?
- 用在权限管理,比如对于的角色展示不同的页面。比如普通用户显示部分的路由组件,对于管理员显示全部的路由组件。
- 如何动态的添加或者删除路由?
- 通过router.removeRoutes(),在进行角色的判断以后,执行添加或者删除路由的操作。
$route和$router
- route是局部路由对象**,当前激活的路由信息对象,用于获取当前路由的信息。包含params、query、name、path、hash、fullpath等路由信息参数。
- router是全局路由对象**,包括所有的路由、编程式路由的跳转方法push||replace
路由跳转方式:声明式导航和编程式导航
- 声明式导航:通过router-link标签的to属性指定目的路由实现跳转,配合router-view渲染相应的组件内容。适合场景:结构中使用a标签的形式,没有什么复杂的业务逻辑。
- 编程式导航:通过this.$router.push||replace方法进行路由跳转。用于跳转条件比较复杂的情况,结构中不适用a标签,或者n秒以后跳转,使用起来比较灵活。
router-link和router-view
- 是什么?router-link是路由导航作用,router-view是组件内容渲染。
- router-link默认生成一个a标签,通过设置to属性跳转path。router-view是显示组件的占位组件,配合name可以显示具名组件。
路由传递参数:query和params
- query;是拼接在url后面的参数,(?a=xx&b=xx)
- 不需要在路由中进行配置
- 路由跳转时指定目标路由时既可以使用path也可以使用name
- params:是路由的一部分。(路由/xxx/xxx/...)
- 必须在路由配置中进行占位
- 路由跳转时只能使用命名路由name
路由守卫
- 全局路由守卫:路由实例上直接操作的钩子函数,所有路由配置组件都会触发,只要触发路由就会触发这些钩子函数。
- 前置路由守卫:router.beforeEach((to,from,next)=>{}):路由跳转前触发,主要使用场景:登陆验证。
- 后置路由守卫:router.afterEach((to,from)=>{}):路由跳转完成以后触发,使用场景,比如跳转完成以后根据组件名称改变页签上显示的title
- 独享路由守卫:在指定路由配置中使用,beforeEnter属于路由中配置项
- beforeEnter:(to,from,next)=>{}:和beforeEach执行时机相同,如果同时设置beforeEach则会紧随其后执行。
- 组件内路由守卫:在组件内执行的钩子函数,类似组件内的生命周期
- beforeRouteEnter(){}:通过路由规则进入组件时调用:不能访问组件实例,在beforeCreate生命周期之前触发
- beforeRouteLeave(){}:通过路由规则离开组件时调用
- beforeRouteUpdate(){}:当前路由改变但是组件被复用的时候调用。比如一些带有动态参数的路径。
vue-router钩子的执行顺序
- 全局前置守卫:beforeEach
- 路由独享守卫: beforeEnter
- 组件内路由守卫:beforeRouteEnter
- 全局后置守卫:afterEach
- 组件生命周期:beforeCreate、created、beforeMount、mounted
- 组件内路由守卫的next传入的回调函数。
- ...
- beforeRouteLeave
路由懒加载
- 什么是路由懒加载
- 为什么?因为打包应用时,js包会非常大影响页面加载,如果把不同的路由对应的组件分割成不同的代码,当路由被访问到的时候才加载对应的组件,会更加高效。
- 路由懒加载就是当路由被访问到的时候再加载相应的路由。在打包构建应用时,把不同的路由组件分割成不同的代码块,当路由被访问到的时候才加载对应的代码块。缩短了首屏渲染的时间。
- 如果不使用路由懒加载,网页默认打开就加载全部的页面及对应的资源。影响首屏加载速度,用户体验差。
- 路由懒加载怎么做?
- 对所有的路由使用动态导入的方式。
- 将路由对应的组件不再直接import,而是改成写成一个函数,返回import()一个promise对象,当函数调用的时候才会加载对应的组件内容
- 在引入组件时不使用import直接引入,而是在components配置项中配置一个返回Promise的组件实现懒加载 const Foo = ()=>import(''),并且可以指定webpackChuckName,可以把名字相同的组件打包成同一个js文件。
异步组件
- 在大型应用中,需要分割应用为更小的块,并且在需要使用这个组件的时候再加载它们,不仅可以在路由切换的时候使用懒加载,实现按需加载,还可以在页面组件中使用异步组件,实现更细致的分割。
- 使用
- 在注册组件时,给异步加载的组件指定一个loader函数,使用ES模块化语法import实现动态导入。
'my-component': () => import('./my-async-component')
- 可以使用工厂函数,指定loadingComponent和errorComponent选项,即异步组件加载时使用的组件,加载失败使用的组件。
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000 })
- vue3中结合suspense实现
Vuex
- 是什么 vuex是vue的状态管理工具。用于集中式的管理所有组件的状态,解决多组件的数据通信。简单地说,相当于一个仓库,存放着共享数据,所有组件都可以使用这些数据。
- 为什么用vuex?优点 如果开发中频繁使用组件间通信的方式传递数据,会导致数据的流向复杂,难以管理和维护。
- vuex的原理
- 利用了vue的插件,将数据存放到全局的store,并且还使用mixins,将store挂载到每一个vue组件实例中,然后放在data中这样初始化的时候会实现数据的响应式。
- 首先安装vuex的时候,会调用install方法,方法中利用单例模式只能被装载一次;
- 然后使用mixin混入机制,在beforeCreate生命周期前混入vuexInit方法
- 这个方法中把store注入组件实例中
- 借助了data响应式,把state存入data中,
- 哪些属性
- state用于存放数据
- mutations用于操作state数据,是同步的
- actions用户发起异步请求,用于提交mutation
- gettersvuex中的计算属性,存放基本数据中的派生数据
- modules模块化vuex
- vuex是单向数据流还是双向? vuex是单向数据流,组件可以使用vuex中的数据,但是不能修改里面的数据
mutations和actions的区别
mutation是唯一修改状态的方式,actions只能通过提交mutation修改状态
- 概念不同
- 官方文档中,更改vuex的store中状态(state)的唯一方法就是提交mutations。actions可以包含任何异步操作,但是不能修改状态,需要提交mutations才能变更状态。
- 应用场景不同
- 开发时组要直接修改状态,使用mutation,而actions用于除以一些复杂的业务,异步操作等。
- 使用方式不同
- mutation的回调函数接受的参数是state对象。action和store实例具有相同方法和属性的上下文context对象,因此一般会解构为{commit,dispatch,state},从而方便编码。
Vue中data为什么是函数
- 因为如果data是一个对象,那么里面的数据会被所有的组件实例共享,修改组件1的数据会影响到其他组件的数据。
- 如果data是一个函数,那么每次创建组件实例都会返回一份新的数据,修改数据不会影响到其他组件的数据。
Vue项目中axios的封装
- axios?特点
- axios是一个基于promise的http客户端,基于XMLHttpRequest服务发送http请求。
- 特点:1. 从浏览器创建XMLHttpRequest;2.支持Promise;3.从node.js创建http请求;4.提供了一些并发请求的接口,方便了很多操作。5.支持请求拦截器,响应拦截器,可以对请求响应进行统一处理。6.可以转换请求与响应数据。7.支持取消请求。8.可以自动转换JSON数据。9.支持防CSRF、XSRF
- 为什么要二次封装axios
- 因为在一些大型项目中,要发送的请求很多,每发起一次请求,就需要设置一遍超时时间、请求头、地址、错误处理等等信息,因此需要二次封装
- 如何二次封装axios
- 设置请求接口请求前缀
- 设置请求头与超时时间
- 设置请求拦截器:在请求头中设置token,进度条
- 设置响应拦截器,包含请求成功与失败的回调。
const requests = axios.create({
//配置对象
//基础路径,发送请求的时候,路径中会出现api
baseURL:"/api",
//请求超时的时间限制5s
timeout:5000,
});
//请求拦截器
requests.interceptors.request.use((config)=>{
//如果store中包含了uuid,那么在请求头中携带一个字段(和后台商量好的)
if(store.state.details.uuid_token){
config.headers.userTempId = store.state.details.uuid_token;
}
//如果store中包含有token,则在请求时携带在请求头中带给服务器
if(store.state.user.token){
config.headers.token = store.state.user.token;
}
//开始进度条
nProgress.start();
//config,配置对象,里面的header属性很重要
return config;
})
//响应拦截器
requests.interceptors.response.use((res)=>{
//结束
nProgress.done();
//响应成功的回调函数
return res.data;
},(error)=>{
//响应失败的回调函数
return Promise.reject(new Error('false'));
})
请求传递参数query和params
- query参数不用配置路由,不用占位。
- params参数属于路径中的一部分,需要配置路由。
项目中如何使用Mock.js
- 准备好json格式数据,引入Mock,通过Mock.mock()方法设置请求地址和请求结果。包含请求状态码,请求数据。
//mock数据:第一个参数请求地址 第二个参数:请求数据
Mock.mock("/mock/banner",{code:200,data:banner});
//模拟首页大的轮播图的数据
- mockRequest,二次封装专门用于发mock请求的axios,配置baseUrl为'/mock'
- 使用封装好的axios发送请求。
数据双向绑定
- 什么是双向绑定 vue的双向绑定是一个指令v-model,可以绑定一个响应式数据到视图,这样数据改变视图更新,视图变化数据也会更新。
- 为什么使用双向绑定 v-model是语法糖,默认情况下相当于:value和@input,使用v-model可以减少大量繁琐的事件处理代码,提高开发效率。
- v-model原理
- :value实现单向数据绑定,即响应式数据,修改数据,更新视图。
- @input实现修改视图同时数据改变。即通过监听用户的input事件,把input中的value属性赋值给响应式数据,实现输入内容改变,数据改变。
- 因此v-model将这两者进行结合,首先获取到input节点的v-model属性获取到要绑定的数据,再将input.value赋值为对应的响应式数据,这样数据修改,视图更新。再监听input事件,获取input.value的改变,修改响应式数据的值为input.value达到数据的双向绑定。
- 对不同的输入元素会使用不同的属性并且抛出不同的事件
- 比如text 和textarea,输入文本,通过value数据绑定,监听input事件
- checkbox和radio,使用绑定checked属性,监听change事件
- select字段,绑定value属性,监听change事件。
- 使用场景
- 通常在表单元素中使用v-model,也可以在自定义组件中使用。
- v-model三种修饰符,写在v-model后面。.lazy:表示移除焦点以后再更新输出,.number:输出的值只要数字类型的,.trim,去掉输出中多余的空格。
- 对于checkbox,可以用true-value和false-value指定特殊的值
- 对于select,在options选项中指定特殊的值
vue是如何实现响应式的
- 什么是响应式
- 响应式就是修改数据,视图会同步更新。省去了复杂的dom操作。
- vue中为什么要实现响应式?优点
- 因为MVVM框架中要解决的一个核心问题就是要连接数据层与视图层,这样就能够通过数据驱动应用,数据变化,视图更新。而vue的响应式可以让开发人员只需要操作数据,关心业务,不用接触繁琐的DOM操作,大大提高了开发效率,降低开发难度。
- 如何实现响应式?
- 结合数据劫持和发布订阅这模式实现的。通过设置Observer监听器,劫持并监听所有属性变化通知订阅者watcher。Watcher订阅者接收到属性的变化通知,执行update,Compile指令解析器,解析templete为render函数。
- 具体实现:首先初始化数据的时候,执行observe()方法,判断数据类型是数组还是对象。对象的话会new一个Observer()。
- 然后通过Observer实例中会调用this.walk()不断递归遍历对象中的属性,使用defineReactive()方法中的Object.defineProperty()方法为每一个属性添加getter和setter,当数据被访问或发生变化时,就能被监听到,并且做出响应,更新视图。同时defineReactive方法还会给每个属性实例化一个Dep,当访问到数据触发getter中的dep.depend()方法进行依赖收集,当依赖被修改的时候会触发setter中的dep.notify通知watcher方法更新视图。
- 如果是数组,则会对数组原型的七个方法重新包装,如(push,shift,pop,unshift,reverse,splice,sort),除了要有原来的原型方法,并且能够在执行方法的同时更新视图。
- 一些细节
- defineReactive方法还会给每个属性实例化一个Dep。getter中做的事情,依赖收集,访问到属性的时候,通过dep.depend收集依赖。(发布订阅模式)把依赖放在消息订阅器中。
- setter:属性被修改的时候,通过dep.notify方法通知watcher。
- watcher通过update方法更新视图,update方式执行render函数,返回虚拟DOM,然后通过patch函数转变为真实DOM。
vue2.0中的响应式有哪些缺陷
- 新增或者删除属性,不会被监测到,因为Object.defineProperty只能拦截到读取和修改属性。需要使用<Vue.set/delete>这样的特殊api才能生效。
- vue会在初始化实例中劫持数据,因此必须定义在data中
- 不具备监听数组的能力,无法监测到通过索引改变数组元素。而是需要重写数组原型方法。
- 原因:如果知道数组的长度,理论上是可以预先给所有的索引设置getter和setter的,但是很多场景下无法知道数组的长度,其次如果是很大的数组,预先给每一个索引添加getter和setter新歌能负担比较大。
- Object.defineProperty无法一次性监听对象的所有属性,深度监听必须遍历或者递归,对于性能的影响较大。
vue3.0响应式的改进?特点?
- 基于Proxy和Reflect实现,proxy可以拦截对象中任意属性的变化,包括属性的读写、添加、删除,而reflect可以对源对象的属性进行操作。
- 可以原生监听数组。
- 不需要一次性的遍历data属性,可以显著的提升性能。因为可以利用proxy实现一个懒处理,当访问到的时候才对对象做深层次的递归,大大提高了初始化效率。
- 但是proxy是es6新增属性,有些不支持。
虚拟DOM
- 什么是虚拟DOM
- 虚拟DOM是对真实DOM的抽象,用一个js对象来模拟真实DOM
- 里面包含哪些属性
- tag标签名:div...
- props属性:id,class...
- children子元素
- 为什么要使用虚拟DOM
- 因为真实的DOM很大很慢,操作DOM的代价较高,如果频繁操作会导致页面卡顿。并且频繁的操作dom会引起页面的重绘或者回流,因此多个DOM命令执行以后,先进行虚拟DOM的更新,再一次性的映射到真实DOM上,可以有效减少直接操作dom的次数,减少页面的重绘或者回流。
- 直接操作dom是有限制的,对于diff和clone等操作,真实的元素上有很多内容,会额外diff和clone一些没必要的内容。因此如果把这些操作在js对象中进行操作,会比较容易。
- 虚拟dom的生成,和转化为真实dom的过程
graph TD
templete模板 --> compile编译 --> renderFunction渲染函数 --> 返回虚拟DOM --> patch --> 真实DOM
Diff算法
- diff算法?的作用?
- Vue中的diff算法是patching算法,虚拟DOM要想转化为真实DOM需要通过patch方法进行转化。diff算法用于比较新旧DOM之间的最小变化,做最少的视图更新操作。
- diff算法的必要性
- vue1.0视图中每个依赖都有对应的更新函数,因此不需要虚拟DOM和patching算法。但是过于精细的操作会导致无法承载较大的应用。
- 因此vue2.0中,每个组件只有一个watcher与之对应,这时就需要引入patching算法才能精准找到发生变化的地方。
- diff算法何时执行?
- diff执行时刻是组件内响应式数据变更,触发setter,setter中dep.notify通知watcher,触发实例中的update函数 --> 更新函数会再次执行render函数,render函数返回一个最新的虚拟DOM --> 然后执行patch函数,传入新旧两次虚拟DOM,通过比对新旧虚拟DOM找到变化的地方,转化为对应的DOM操作。
- 具体的执行方式?
- 深度优先,同层比较
- 首先比较两个节点是否是同类节点,标签名和key是否相同。如果不相同就删除重新创建。
- 如果双方都是文本节点,就更新文本内容
- 如果都是元素,就递归更新子元素
- 新子节点是文本,老子节点是数组,清空数组,设置文本
- 新子节点数组,老子节点是文本,清空文本,创新数组中这些元素
- 都是文本就更新文本
- 都是数组,都有子节点,需要执行update children的操作,vue2中的优化是进行一个双端算法,尝试从数组的首、尾部分进行比较,因为在首尾找到相同节点的概率比较大。
- **vue2.0中的diff算法有什么缺点?
- 传统的diff算法,会对vnode中所有的children进行判断,全部diff,但是传统的diff算法会对静态节点也进行处理,但是静态节点不会变化,因此对静态节点的diff比较没有意义。
- vue3中diff算法的优化
- vue2.0中的diff算法无法判断一个节点是静态的还是动态的,因此在vue3.0中为vnode添加了额外的信息,区分节点是静态的还是动态的。编译优化,patchFlags:在生成Vnode以后对每一个元素做PatchFlags标记。比如节点的TEXT、CLASS、STYLE...这些动态属性 因此在对比新旧dom进行diff算法的过程中,可以根据具体哪些属性发生变化,是文本发生变化?还是class发生变化?还是子元素变化?进行有目标的diff而不是全部diff。
- Block:在加上patchFlags以后,标记了虚拟节点,然后就可以将这些节点提取出来,保存在这个虚拟节点到dynamicChildren(动态子元素)数组中,具有dynamic children属性的虚拟节点就是block。这样在更新一个block时,只用直接取出dynamicChildren进行更新,这样就忽略掉了不用对比的静态节点。
<template>
<div :class="classNames">{{name}}</div>
</template>
createElement(
"div",
{ class: classNames },
[name],
PatchFlags.TEXT | PatchFlags.CLASS );
从template到render发生了什么?
- compile的过程,编译器
- 编译器,compile,将template编译为render()函数
- 执行编译器的必要性
- 因为html语言写视图比较直观,所以需要编译器将模板编译为可以执行的render()
- 编译器的工作流程
- 解析template模板,执行baseParse函数的过程,将模板初步转化为ast树(抽象语法树),用于描述模板结构,是一个js对象,和虚拟dom比较像但是还不是最终的虚拟dom。
- 对ast树进行进一步的加工和转化,调用了一个tansform函数,里面包含了各种transform操作,比如对指令、节点、表达式进行深度解析。
- generate代码生成:遍历这个ast树,拼接,new function转化为render渲染函数。
- 什么时候执行编译器?
- 预打包环境。在打包阶段执行编译
- 开发环境。组件运行和创建阶段。
Vue实例挂载的过程中发生了什么
- 初始化的过程(beforeCreate,created)
- 创建组件实例,根组件-子组件等等的实例创建
- 初始化组件状态,这些组件中事件、props、methods、data、computed、watch。
- 创建各种响应式数据,对data进行响应式的处理,通过Object.defineProperty给数据设置setter和getter
- 视图更新机制(beforeMount/mounted)
- 初始化之后会调用$mount挂载组件,需要进行Compile的过程将template转化为render函数,执行一次更新函数,render-patch,生成真实dom,初始化渲染页面。
- 在render函数被执行的时候会收集依赖,建立响应式数据和组件更新函数之间的依赖关系。因为init初始化的时候通过Object.definProperty进行绑定,这样当设置的属性被读取,就会触发getter通过dep.depend方法进行依赖收集。这样当数据发生变化的时候,就可以被检测到然后触发setter,然后通知对应的watcher去更新,渲染视图。
Vue的异步更新机制是如何实现的?
当响应式数据发生变化,Vue不会立刻更新DOM,而是开启一个队列,将组件更新函数保存在队列中,然后会在所有的同步代码执行完成之后清空队列,这样同一个事件循环中发生的所有数据变更会异步的批量更新。
- 总体是利用了js的事件循环机制,将更新操作放入异步任务队列实现的
- 响应式数据更新后,会调用dep.notify方法通知watcher执行update方法,不是直接更新视图而是收集所有需要更新的watcher放入一个watcher队列中(进行一些watcher去重的操作),然后将刷新函数放入异步任务队列中(任务队列中一次只能执行一个刷新函数)。(通过nextTick函数放入的)。
- 这样就实现了数据变更以后,将所有需要更新的watcher收集起来批量更新,达到异步更新的效果。
$nextTick
- 作用 nextTick是等待下一次dom更新的工具函数。 nextTick里面的函数会在下一次dom更新之后执行。
- 为什么要使用nextTick
- Vue的异步更新策略,执行js代码的时候不会立刻更新dom,而是等js代码执行完之后批量更新。因此在一些需要基于更新以后的dom进行操作的场景下,需要使用nextTick等待dom更新之后在进行。
- 开发中哪些场景需要使用nextTick
- 比如在created中想要获取dom进行操作
- 响应式数据更新以后,需要获取新的dom,或者操作dom。比如点击编辑按钮,出现input框,这时有一个需求是需要给这个input框增加placeholder聚焦,就需要使用nextTick,等待input框出现以后再添加聚焦。
- 或者需要获取列表更新以后的高度
- 用法
- 内部添加回调函数
- await执行结果,返回一个Promise
- 内部实现原理
- 基于js的事件循环机制实现的。因为需要在ui-render之后执行nexttick中的回调函数,然后ui-render会在执行完所有的微任务之后执行,因此总的来说需要将nextTick中的callback放入更新dom的那个微任务的后面执行即放到刷新函数的后面,这样队列中已经存在刷新函数的话就会等待刷新函数执行完之后再执行。这样就能保证执行nexttick的时候所有的dom操作已经执行完毕了。
- 因为兼容性的问题,vue还做了微任务向宏任务的降级方案,队列的最佳选择是微任务的Promise,但是不支持Promise的就会使用宏任务作为一个降级策略,也可以实现下一次的UI-render结束之后再执行。降级方案依次是setImmediate,MessageChanel,setTimeOut
- nextTick中传入的callback会被放到刷新函数的后面,这样队列会等刷新函数执行完毕以后,所有的DOM操作也就结束,callback能够获取到最新的dom。
- 具体一点,nextTick中传入的回调函数会使用try catch包裹以后,放入callback数组中,然后这个数组会被加入浏览器的异步任务队列中。但是会被放在刷新函数的后面,这样已有刷新函数的情况下会等待刷新函数执行完成之后,所有的dom操作结束,这样callback就能够获取到最新的dom。
Vue中如何扩展一个组件
- 为什么要扩展一个组件
- 组件复用,组件功能扩展,同一个组件可以实现多个功能
- 组件扩展方法
- 逻辑扩展:mixins、hooks封装composition API(vue3)
- 内容扩展:slot
- 各自的优缺点
- mixins:可以实现多个组件共同的配置提取为一个混入对象,但是混入的数据或者方法可能会和当前组件中的内部变量命名产生冲突。
- vue3中hooks封装composition API可以解决。创建独立的响应式模块,编写独立的逻辑,在setup选项中组合使用,可读性和可维护性好。
- slot插槽,组件内容不确定,需要从外面通过分发扩展的方式来传递内容。
slot
- slot:子组件内容结构和内容不确定,需要父组件向每个插槽中传递结构。
- 默认插槽
- 具名插槽:有名字的默认插槽。场景:有多个结构和插槽,因此用来区分不同的slot,需要将结构精确分发到不同的位置,使用具名插槽
- 作用域插槽:父组件template中可以设置slot-scope,接受来子组件slot传来的数据。
- slot的实现原理: 当子组件vm实例化时,获取到父组件传入的slot标签内的内容,存放到vm.slot.default,具名插槽位vm.slot中的内容进行替换。并且如果是作用域插槽,可以位插槽传递数据。
Vue中常用的修饰符有哪些?
修饰符用于处理一些DOM事件的细节,可以让我们不用花大量时间处理dom细节,专注于业务逻辑。
- 表单修饰符:
- v-model.lazy:当用户输入完成,光标离开输入框,才对value进行赋值
- v-model.trim:自动过滤用户的输入内容,修剪掉两边的空格
- v-model.number:自动将用户的输入值转换为数值类型
- 事件修饰符:对事件捕获和目标阶段进行处理
- 阻止事件冒泡:@click.stop:阻止事件冒泡,相当于event.stopPrepagation()
- 阻止事件默认行为.prevent:例如a标签的自动跳转行为,或者阻止表单submit的自动提交行为。相当于event.preventDefault()
- .self只有当e.target是当前元素自身触发的时候才进行处理
- .once绑定了事件以后只会触发一次
- .capture :在事件捕获阶段处理目标事件,从包含这个元素的顶层往内部触发
- 鼠标按钮修饰符
- .left:左键点击
- .right:右键点击
- .middle:中键点击
- 键盘修饰符
- .keyCode:用于修饰键盘事件onkeyup,onkeydown
- v-bind修饰符
Vue中的性能优化方法有哪些?
- 路由懒加载
- keep-alive组件缓存
- 使用 v-show避免重复创建节点
- v-for和v-if 不同时使用
- v-once 和v-memo不再变化的数据使用v-once,表示不再变化的数据只渲染一次,以后不再变化视为静态内容。v-memo按条件跳过更新。比如列表中只会更新状态发生变化的项。
- 长列表性能优化,大数据长列表,可以采用虚拟滚动,只渲染少部分区域的内容。可以使用一些开源库。
- 事件的销毁:Vue组件销毁只会销毁组件本身的事件,因此像定时器之类的需要手动销毁。
- 图片懒加载:对于图片比较多的页面,为了提高页面的加载速度,可以使用图片懒加载
- 按需引入第三方插件,比如element-ui等ui库
- 服务端渲染,如果首屏渲染慢,可以考虑SSR
Vue渲染大量数据使应该如何优化?
- 大量数据渲染,会在页面中出现过多的dom节点,加载页面时容易出现卡顿的现象。
- 通常采用分页器的方式,避免一次性的渲染大量数据
- 采用虚拟滚动方案渲染数据,使用vue-virtual-scroller插件等虚拟滚动方案,只渲染视口范围内的数据。
- 如果不需要更新的数据使用v-once之渲染一次
- 使用v-memo缓存结果,按条件跳过更新。避免数据变化时一些不必要的dom变化
- 采用懒加载的方式,比如下拉刷新等。在用户需要的时候再加载数据。
- 避免大量数据的获取和渲染,如果业务中需要展示大量数据,可以采用分页器,或者不需要更新的数据用v-once,需要更新的可以使用v-memo指定按条件更新。如果数据量巨大可以采用虚拟滚动的方案,还有一些其他交互优化方式比如懒加载。
Vue长列表性能优化
- 为什么长列表性能优化?
- 场景:列表数据很多条,几千条上万条,页面中会创建非常多的html节点,浏览器性能消耗巨大,用户体验会很差。一般使用懒加载,当滚动条触底进行加载,但是如果数据量巨大的情况,依然会出创建非常多的节点,造成页面卡顿。
- 虚拟滚动思路
不把长列表数据一次性的全部显示在页面上,而是只把可视区域内的列表数据渲染出来。
- 截取长列表的部分数据用来填充屏幕容器区域
- 长列表不可见部分使用空白占位填充
- 监听滚动事件,根据滚动位置改变可视列表
- 监听滚动事件,根据滚动位置动态改变空白填充
- 虚拟滚动具体思路?
- 根据滚动事件监听列表的开始Index和结束Index
- 动态的从startIndex和endIndex截取请求中获取的所有数据展示在页面中。这样页面中只会出现一定数量的节点
- 计算上下留白的高度,使用padding动态的设置上下内边距,填充列表剩余的位置。具体计算:上padding:startIndex X 每条数据高度。下padding:列表总数据条数减去endIndex X 每条数据的高度。
- 为列表动态的设置样式,动态的添加上下内边距。
SPA与MPA
SPA和MPA是什么?
- SPA是单页面应用,只有一个主页面,通过动态重写当前页面来与用户进行交互。react,vue,angular都属于SPA
- MPA多页面应用,每一个页面都是主页面,每次访问另一个页面都需要重新加载页面资源。
SPA与MPA区别
- 组成:SPA只有一个主页面,包含多个页面片段。MPA包含多个主页面
- 刷新方式:SPA局部刷新页面,MPA整页刷新
- 首屏加载:SPA首屏加载较慢,切换较快。MPA首屏加载较快,切换时因为是整页刷新所以切换加载资源速度慢。
- SEO友好程度:SPA模式SEO不友好,可以通过使用SSR或者预渲染方式改善。MPA应用SEO友好。
解决SPA应用的SEO不友好问题
- 为什么SPA应用SEO不够友好?
- 因为单页面应用只有一个主页面,搜索引擎只能搜索到一个页面,相反多页面应用就有更多的页面可以被搜索引擎抓取到
- 一个页面仅有一个关键词,也不利于搜索引擎的抓取。可以用vue-meta-info解决
- 单页面应用在打开页面的时候页面中没有数据,只有页面加载前有数据才能够被爬虫爬取。打开页面-->发情求-->返回data页面渲染。
预渲染
- 预渲染技术通过prerender-spa-plugin实现加载前页面中有数据,但是无法配置动态路由。
- 场景:需要SEO页面只是少数几个。
SSR
- 是什么:
- 服务端渲染技术,将组件或者页面通过服务器生成html字符串,再返回给浏览器,最后将这些静态标记激活为客户端上完全可以直接交互的应用程序。
- 服务端渲染客户端请求服务器的时候,服务器到数据库中获取到相关的数据,并且在服务器内部将Vue组件渲染成HTML,生成html字符串,一并将数据和HTML返回给客户端。这个服务器将数据和组件转化为html的过程叫做服务端渲染
- 客户端激活客户端接收到数据和html以后,由于已经存在数据,客户端不需要再次请求数据,只需要将数据同步到组件和Vuex内部。由于已经有html结构,所以客户端在渲染组件时只需要将html的Dom节点映射到vNode,不需要重新创建dom节点。这个将数据和html同步的过程叫做客户端激活。
- 解决的问题
- seo,因为搜索引擎优先爬取页面的HTML结构,使用ssr,服务器端已经生成了和业务数据中相关联的html页面,因此可以被爬虫爬取到。
- 并且包含多个页面可以被爬虫爬取的数据更多,可以对每个页面配置关键词。
- 白屏时间更短,首屏加载更快:因为相对于客户端渲染,服务器在浏览器请求url之后就已经得到了一个带有数据的html文本,所以只需要解析html然后构建dom树,不需要请求数据了。而客户端渲染,先得到一个html空页面,之后还需等待执行js,请求后端获取数据,js渲染页面等一系列过程才可以看到最后的页面。就会导致比较复杂大型的项目,由于加载的js更多,会导致首屏加载时间非常长,用户体验较差。
- 缺点
- 整个项目复杂性
- 性能问题
- 服务器负载。相对于前后端分离后端只用提供静态资源,服务器的负载更大。
- 怎么实现SSR
- 通过webpack配置,分别对客户端和服务端进行打包生成bundle,然后使用客户端激活
- 或者直接使用nuxt
vue中为什么只能有一个根节点?
- vue2中组件中只能有一个根节点,vue3中可以有很多个根节点
- 由于vue2中的虚拟dom是单根的树形结构,patch方法在进行diff算法时只能从根节点开始遍历。
- 但是在vue3中引入了fragment概念,因此可以设置多个根节点。如果发现组件有多个根节点,就创建一个fragment节点,把多个根节点都作为fragment的children。
Vue3.0中ref和reactive的区别
- ref函数
- ref函数的作用是定义一个响应式的数据,会创建一个包含响应式数据的引用对象,读取和操作数据需要访问value属性。
- ref函数接收的数据可以是基本数据类型也可以是对象类型。基本数据类型的响应式是通过Object.defineProperty()的get和set完成,对象类型的数据是利用了reactive函数实现的,原理是通过Proxy数据劫持,通过Reflect操作数据。
- reactive函数
- 定义对象类型的响应式数据,基本类型用ref处理。会接收一个原对象转换为一个代理对象。
- 不需要通过.value属性访问响应式数据
- 原理是通过proxy数据劫持 ,通过Reflect操作数据
- 区别
- 定义的数据类型不同:ref通常用于定义基本数据类型,但是也可以用于定义引用数据类型,但是ref定义引用数据类型原理上是通过reactive函数实现的。reactive用于定义引用数据类型。
- 原理:ref实现响应式通过Object.defineProperty()实现,内部创建一个包含getter和setter的RefImpl类。reactive通过proxy和Reflect实现
- 使用上:ref定义的响应式数据需要通过.value属性才能访问到(模板中不用)。reactive定义的数据都不需要通过value属性。
Vue3.0中watch和watchEffect的区别
- Watch既需要指明监视的属性,也需要指明监视的回调。wtchEffect不需要指明监视的哪些属性,监视的回调中使用到哪个属性就会自动监视哪个属性。
- 使用场景
- watchEffect传入的函数既是监测数据源,也是回调函数。如果不关注响应式数据变化前后的值(oldValue,newValue)就可以使用watchEffect
- watch需要指明监视的数据源,可以进行更加精确的监视,并且能够获取到数据变化前后的值
- Watch在默认情况下不会执行回调函数,只有数据发生变化才会执行回调函数,可以通过配置项设置immediate为true设置立即执行一次。watchEffect使用时传入的函数会立即执行一次。
- 实现原理。watch中被监视的数据源和回调函数是需要单独传入的,而watchEffect不需要传入回调函数,对于watchEffect来说数据源和回调函数其实是一个。
Vue3.0的设计目标与优化方案
- 设计目标:
- Vue2.0的问题:随着功能的增长,复杂的代码会变得难以维护。多个组件之间的提取和复用能力有待提升(比如mixins,会造成命名冲突的问题)。类型推断不友好。
- Vue3.0的改进:易用性,框架性能,扩展性,可维护性,开发体验。
优化方案
-
复用性:
- vue2中通过mixin实现功能的复用和提取,但是会造成命名冲突和数据来源不清晰的问题。vue3.0中通过compositionAPI的方式,通过自定义hook函数将setup中使用的compositionAPI进行了封装,不会出现命名冲突的问题。
-
可维护性:
- OptionsAPI变成compositionAPI,optionsAPI将各个功能的代码混成一起,当项目比较大逻辑比较复杂的时候不好维护,代码可读性也不强。compositionAPI将相同功能的代码编写在一起,更好维护。
-
性能优化:
- vue2.0通过Object.defineProperty实现响应式,有一些弊端比如对于对象的新增和删除不能被监测到,也不能够原生的监听数组,嵌套层级比较深时需要一次性递归遍历等等。vue3.0基于Proxy实现响应式,能够监听对象的增删改读,也能够原生的监听数组,并且不需要一次性的递归,而是实现一种按需操作,当访问到的时候才会变成响应式,性能更好。
-
编译优化:diff算法的优化、静态提升、事件监听缓存、SSR优化
- diff算法优化:vue2.0中每个组件对应一个Watcher实例,会在组件渲染的过程中把用到的数据记为依赖,当依赖发生变化触发setter通知watcher更新视图。更新视图会执行patch方法,然后通过diff算法比较新旧vnode的不同,但是vue2.0中的diff算法会对组件的节点全部遍历,但是如果有些节点是静态节点是不需要遍历的。vue3.0就对diff的过程做了优化。通过patchFlags对节点进行区分,区分静态节点和动态节点,在更新时只会对比带有标记的节点,并且patchFlags定义了许多种类型,可以对节点进行更加精确的比较。 还会将加上patchFlags标记的节点放在dynamicchildren数组中,这样下次diff只需要从数组中取出节点进行比较即可。
- 静态提升:把静态节点提升到render函数外,这样再次调用render函数时静态节点就不参与diff。
- 事件监听缓存:默认情况下绑定事件会被视为动态绑定,每次更新视图都会追踪它的变化。但是正常情况下渲染前后的事件都是同一个事件,因此不需要追踪变化,vue3.0利用事件监听缓存,不给事件标签加标记,不再比较变化。
- SSR优化:当静态内容大道一定两级的时候,会调用createStaticVNode方法在客户端生成一个static node,将静态标签转化为文本,生成HTML字符串
-
源码体积:
- 移除了不常用的API: 1、 比如组件通信方式中的listeners,refs访问标记的子组件。
2、vm.delete方法用来向数组或者对象中添加元素触发响应式,vue3.0中用proxy本身就能监听到添加方法因此不需要这个方法了。
3、 vm.destory方法销毁组件,vue3.0中destoryed被命名为unmounted。filter过滤器被删除,vue2.0中可以使用过滤器处理文本格式,vue3.0中过滤器被删除,可以使用计算属性或者调用方法来替换。
- 整体体积变小:Tree-shaking,所有函数只有在用到的时候才进行打包。基于vite。
-
编程体验
- 新增的API和组件:组件如Fragment(虚拟节点代替div,可以不需要根节点,少了很多的div元素)、Teleport(传送,将组件移动到指定位置,在一些嵌套比较深的组件中引入外部组件,有可能会影响外层组件的样式和DOM结构,不利于维护)、Suspense(异步组件,异步组件加载时渲染一些后备的内容)
- 将全局的API从Vue调整到app上
- TypeScript支持
Options API和Composition API
- Options API
- vue文件中通过定义methods、computed、watch、data等属性和方法共同处理页面逻辑。
- 随着组件变得复杂,导致所有的功能混合在一起,导致代码可读性不强。
- mixins,命名冲突,数据来源不明
- Composition API
- 一个功能所有定义的API会放在一起(高内聚,低耦合)
- 代码逻辑与复用
- 几乎不使用this,减少this指向不明的情况
- 都是函数形式,typescript支持
Vue3中的treeshaking是如何做的?
- 基于ES6 Module的静态编译思想,在编译的时候就能确定模块的依赖关系,判断哪些模块被加载,哪些未被使用,从而删除对应的代码。