Vue面试题整理(二)
29. 说说你对双向绑定的理解,以及它的实现原理吗?
29.1 双向绑定的概念
vue中双向绑定靠的是指令v-model,可以绑定一个动态值到视图上,同时修改视图能改变数据对应的值(能修改的视图就是表单组件)经常会听到一句话:v-model是 value+input 的语法糖。
29.2 表单元素中的v-model
内部会根据标签的不同解析出不同的语法。并且这里有“额外”的处理逻辑
- 例如 文本框会被解析成 value +input事件
- 例如 复选框会被解析成 checked+change事件
29.3 组件中的v-model
组件上的 v-model
默认会利用名为 value
的 prop 和名为 input
的事件。对于组件而言 v-model 就是value + input 的语法糖。可用于组件中数据的双向绑定。
名字也可以修改:
Vue.component('base-checkbox',{
model:{
prop:"checked'
event: 'change
},
props:{
checked: Boolean
},
template:`
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
那组件中如果有多个数据想做双向数据绑定怎么办?很遗憾在vue2中不支持使用多个v-model的。vue3中可以通过以下方法进行绑定。
<my v-model:a="a" v-model:b="b" v-model:c="c"></my>
30. Vue 中 .sync
修饰符的作用?
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”,这时可以使用 .sync
来实现,v-model
默认只能双向绑定一个属性,这里就可以通过.sync修饰符绑定多个属性。
`<my :value.sync= xxxx"></my>` ;
// 编译的结果是 with(this){return _c('my',{attrs:{"value":xxxx},on:{"update:value":function(Sevent){xxxx=Sevent}}}}
vue3 中.sync 语法被移除
31. Vue 中递归组件理解
<el-menu>
<el-menu-item>根1</el-menu-item>
<el-submenu>
<template slot="title">根2</template>
<el-menu-item>根2-1</el-menu-item>
<el-menu-item>根2-2</el-menu-item>
</el-submenu>
<el-menu-item>根3</el-menu-item>
<el-menu-item>根4</el-menu-item>
</el-menu>
1.1 模板递归
<el-menu>
<template v-for="item in data">
<resub :data="item" :key="item.id"></resub>
</template>
</el-menu>
编写递归组件 resub,在组件中调用自己
<el-submenu :key="data.id" v-if_"data.children">
<template slot-"title">{{data.title}}</template>
<template vfor="item in data.children">
<resub :key="item.id" :data="item"></resub>
</template>
</el-submenu>
32. 组件中写 name 选项有哪些好处及作用?
- 增加 name 选项会在components属性中增加组件本身,实现组件的递归调用。
- 可以标识组件的具体名称方便调试和查找对应组件。
- children.filter(item=>item.options.name === 'xxX')
Sub.options.components[name] = Sub;//子组件会通过name属性,将自己也注册到组件中
33. Vue 常用的修饰符有哪些有什么应用场景?
- 表单修饰符 lazy、trim、number
- 事件修饰符 stop、prevent、self、once、capture、passive、Vue2中的
.native
(Vue3弃用) - 鼠标按键修饰符 left、right、middle
- 键值修饰符 对 keyCode 处理
- Vue2中的
.sync
(Vue3弃用)
34. 自定义指令的应用场景
34.1 指令的概念
Vue 除了内置指令之外,同时 vue 也允许用户注册自定义指令来对 Vue 进行扩展。指令的目的在于可以将操作 DOM 的逻辑进行复用。
34.2 指令的生命周期
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted
:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)。update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。 指令的值可能发生了改变,也可能没有。componentupdated
:指令所在组件的 VNode 及其子VNode 全部更新后调用。unbind
:只调用一次,指令与元素解绑时调用。
34.3 常见的指令编写
- 图片懒加载 v-lazy
- 防抖 v-debounce
- 按钮权限 v-has
- 拖拽指令 v-draggable mousemove.mouseup monsedown.dragenter、dragover、drop。可视化拖拽编辑器
- 点击事件处理 v-click-outside
<div v-click-outside="hide">
<input type="text" @focus="show" />
<div v-if="isshow">显示面板</div>
</div>
vue.directive(clickoutside,{
bind(el, bindings, vnode) {
el.handler = function(e){
if(!el.contains(e.target)){
let method = bindings.expression;
vnode.context[method]();
}
};
document.addEventListener("click", el.handler);
},
unbind(el){
document.removeEventListener("click",el.handler);
},
});
35. 说说你对 nextTick 的理解?
35.1 nextTick 概念
- Vue 中视图更新是异步的,使用 nextTick方法可以保证用户定义的逻辑在更新之后执行。
- 可用于获取更新后的 DOM,多次调用 nextTick 会被合并。
<template>
<div id="counter">{{ count }}</div>
</template>
<script>
export default {
name:"App",
components:{},
data(){
return {
count: 0,
};
},
mounted(){
this.$nextTick(()=>{
console.log(document.getElementById("counter").innerHTML);
});
this.count = 100;
},
};
</script>
36. keep-alive 平时在哪里使用?
36.1 概念
keep-alive 是 vue 中的内置组件,能在组件切换过程会缓存组件的实例,而不是销毁它们。在组件再次重新激活时可以通过缓存的实例拿到之前渲染的 DOM 进行渲染,无需重新生成节点。
36.2 使用场景
动态组件可以采用keep-alive进行缓存
<keep-alive :include="whiteList" :exclude="blackList" :max="count">
<component :is="component"></component>
</keep-alive>
在路由中使用 keep-alive
<keep-alive :include="whiteList" :exclude="blackList" :max="count"><router-view></router-view></keep-alive>
也可以通过 meta 属性指定哪些页面需要缓存,哪些不需要
<div id="app">
<keep-alive>
<!--需要缓存的视图组件 -->
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<!--不需要缓存的视图组件 -->
<router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
36.3 原理
下面我按“面试者回答源码题”的口吻,把 <keep-alive>
在 Vue3(@vue/runtime-core
)中的实现要点完整走一遍。力求让面试官既能听到宏观设计,又能抓到代码细节。
36.3.1 先抛结论
<keep-alive>
是一个抽象组件(abstract: true
),它通过内部缓存 VNode + 组件实例的方式,让被包裹的组件在切换时不被真正销毁,而只是失活 / 激活。
核心数据结构:一个 Map 做缓存、一个 Set 做 key 顺序、一个 div 做离线 DOM 容器。
36.3.2 入口:KeepAliveImpl(Vue3)
源码位置:packages/runtime-core/src/components/KeepAlive.ts
const KeepAliveImpl: ComponentOptions = {
name: `KeepAlive`,
__isKeepAlive: true, // 打标记,运行时专用
props: { include, exclude, max },
setup(props, { slots }) {
const cache: Cache = new Map() // <key, vnode>
const keys: Keys = new Set() // LRU 链
let current: VNode | null = null
...
}
}
36.3.3 缓存、失活、激活 三条主线
36.3.3.1 缓存时机
onMounted
/ onUpdated
钩子内调用 cacheSubtree()
:
const cacheSubtree = () => {
if (pendingCacheKey != null) {
cache.set(pendingCacheKey, getInnerChild(instance.subTree))
}
}
第一次渲染完成时把子树的 VNode 缓存起来,key 的规则:
key = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${tag}` : '')
: vnode.key
36.3.3.2 失活(deactivate)
当路由切走或动态组件切换,父级 unmount
流程会调用 KeepAliveCtx.deactivate
:
sharedContext.deactivate = (vnode: VNode) => {
move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
// 把真实 DOM 挪到离线 div,组件实例仍存活
}
此时组件执行 deactivated
生命周期。
36.3.3.3 激活(activate)
再次进入时命中缓存:
sharedContext.activate = (vnode, container, anchor, ...) => {
move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
// 把离线 div 中的 DOM 直接插回页面
}
同时触发 activated
钩子。
36.3.4 LRU 与 max 控制
每次命中缓存都会把 key 重新 delete -> add
,保证最近访问在最后;
当 keys.size > max
时,删除最久未用的缓存:
if (max && keys.size > parseInt(max as string, 10)) {
pruneCacheEntry(keys.values().next().value)
}
真正 unmount
时会调用原始渲染器内部的 _unmount
并执行组件的 beforeUnmount / unmounted
。
36.3.5 include / exclude 动态裁剪
watch
监听 include、exclude,调用 pruneCache(filter)
对现有缓存做遍历,不符合条件的直接销毁:
watch(
() => [props.include, props.exclude],
([include, exclude]) => {
include && pruneCache(name => matches(include, name))
exclude && pruneCache(name => !matches(exclude, name))
},
{ flush: 'post', deep: true }
)
36.3.6 与 Vue2 差异一句话总结
- Vue2 用
this.cache = Object.create(null)
存组件实例,VNode 与实例混在一起; - Vue3 把缓存粒度细化到 VNode,配合 Composition API 的
setup
,逻辑更纯粹,也支持 Suspense。
36.3.7 面试金句收尾
“Keep-alive 本质是用空间换时间的缓存组件,Map 存 VNode + 实例,Set 做 LRU 顺序,抽象组件让它自身不渲染任何 DOM,只负责把子树的‘生死’接管过来,从而实现秒开与状态保持。”
36.4 keep-alive 中数据更新问题
beforeRouteEnter: 在有vue-router的项目,每次进入路由的时候,都会执行
beforeRouteEnterbeforeRouteEnter(to,from, next){
next(vm=>{
vm.getData() //获取数据
})
},
actived: 在keep-alive缓存的组件被激活的时候,都会执行actived钩子
activated(){
this.getData()// 获取数据
},
37. Vue 中使用了哪些设计模式?
- 单例模式 - 单例模式就是整个程序有且仅有一个实例 Vuex 中的
store
- 工厂模式 - 传入参数即可创建实例(
createElement
) - 发布订阅模式 - 订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心,由调度中心统一调度订阅者注册到调度中心的处理代码。
- 观察者模式 -
watcher
&dep
的关系 - 代理模式 - 代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
- 装饰模式 -
vue2
装饰器的用法(对功能进行增强 @) - 中介者模式 - 中介者是一个行为设计模式通过提供一个统一的接口让系统的不同部分进行通信。
vuex
- 策略模式 - 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案。
mergeOptions
- 外观模式 - 提供了统一的接口,用来访问子系统中的一群接口。
38. Vue 中的性能优化有哪些?
- 数据层级不易过深,合理设置响应式数据
- 通过 Object.freeze()方法冻结属性
- 使用数据时缓存值的结果,不频繁取值。
- 合理设置 Key 属性
- v-show 和v-if 的选取
- 控制组件粒度 -> Vue 采用组件级更新
- 采用函数式组件 -> 函数式组件开销低
- 采用异步组件 -> 借助
webpack
分包的能力 - 使用
keep-alive
缓存组件 v-once - 分页、虚拟滚动、时间分片等策略......
39. 单页应用首屏加载速度慢的怎么解决?
- 使用路由懒加载、异步组件,实现组件拆分,减少入口文件体积大小(优化体验骨架屏)
- 抽离公共代码,采用 splitChunks 进行代码分割。
- 组件加载采用按需加载的方式。
- 静态资源缓存,采用 HTTP 缓存(强制缓存、对比缓存)、使用 localStorage 实现缓存资源。
- 图片资源的压缩,雪碧图、对小图片进行 base64 减少 http 请求。
- 打包时开启 gzip 压缩处理
compression-webpack-plugin
插件 - 静态资源采用 CDN 提速。终极的手段
- 使用 SSR 对首屏做服务端渲染。
40. Vue 项自中你是如何解决跨域的呢?
跨域是浏览器同源策略导致的,这个是浏览器的行为(协议、主机名、端口的不同都会导致跨域问题)。服务端和服务端之间进行通信是没有跨域问题的。跨域的实现方案有很多种。不过一般常用的就那么几种。
- CORS(Cross-Origin Resource sharing,跨域资源共享)由服务端设置,允许指定的客户端访问服务器。
- 构建工具中设置反向代理、使用 Nginx 做反向代理。
- 使用 Websocket 进行通信。
- 搭建 BFF(Backend For Frontend)层解决跨域问题。
41. Vue 项目中有封装过 axios 吗?主要是封装哪方面的?
- 设置请求超时时间。
- 根据项目环境设置请求路径。
- 设置请求拦截,自动添加 Token。
- 设置响应拦截,对响应的状态码或者数据进行格式化。
- 增添请求队列,实现 loading 效果。
- 维护取消请求 token,在页面切换时通过导航守卫可以取消上个页面中正在发送的请求。
42. Vue 要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?
42.1 常见权限控制
- 登录鉴权:用户登录后返回 Token ,前端将 Token 保存到本地,作为用户登录的凭证,每次发送请求时会携带 Token ,后端会对 Token 进行验证。当页面刷新时我们可以使用 Token 来获得用户权限。
- 访问权限:根据用户是否登录判断能否访问某个页面,通过路由守卫实现判断用户是否有此权限。
- 页面权限:前端配置的路由分为两部分“通用路由配置”和“需要权限的路由配置”。在权限路由中增加访问权限 meta(备注)。用户登录后可得到对应的权限列表,通过权限列表筛查出对应符合的路由信息,最后通过 addRoutes 方法,动态添加路由。
- 按钮权限:按钮权限一般采用自定义指令实现,当用户登录时后端会返回对应的按钮权限,在按钮上使用此指令,指令内部会判断用户是否有此按钮权限,如果没有则会移除按钮。
43. Vue 中异步组件的作用及原理
43.1 异步组件概念
Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用。
43.2 异步组件的写法
- 回调写法
{
components: {
"my-component":(resolve,reject)=>{
setTimeout(function(){
resolve({
render(h){
return h('div','hello')
},
});
},1000);
},
},
}
- Promise写法
{
components:{
"'my-component":()=> import(/* webpackchunkName:"B4"*/
"./components/B4.vue"),
},
}
- 对象写法
const Asynccomponent==>({
//需要加载的组件(应该是一个'Promise”对象)
component: import("./MyComponent.vue"),
//异步组件加载时使用的组件
loading: Loadingcomponent,
//加载失败时使用的组件
error: Errorcomponent,
//展示加载时组件的廷时时间。默认值是200(毫秒)
delay:200,
//如果提供了超时时间且组件加载也超时了,则使用加载失败时使用的组件。默认值是:'Infinity'
timeout:3000,
});
43.3 异步组件原理
- 默认渲染异步占位符节点
- 组件加载完毕后调用
$forceUpdate
强制更新,渲染加载完毕后的组件
44. Vue-Router 有几种钩子函数,具体是什么及执行流程是怎样的?
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫(2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next的回调函数,创建好的组件实例会作为回调函数的参数传入。
45. Vue-Router 几种模式的区别?
- Vue-Router 有三种模式 hash、history、abstract
- abstract 模式是在不支持测览器 AP|环境使用,不依赖于浏览器历史
- hash 模式:hash+ popstate/hashChange 兼容性好但是不够美观,hash 服务端无法获取。不利于 seo优化
- history 模式:historyApi+ popState 美观,刷新会出现404->CLl webpack history-fallback
46. Vue 项目本地开发完成后部署到服务器后报 404 是什么原因呢?
history 模式刷新时会像服务端发起请求,服务端无法响应到对应的资源,所以会出现 404 问题。
47. 谈一下你对 vuex 的个人理解
47.1 概念
- Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,采用集中式存储管理应用的所有组件的状态。核心就是解决数据的共享。
- 以相应的规则保证状态以一种可预测的方式发生变化。
47.2 状态修改
- 组件中 commit() -> mutation -> 修改状态
- 组件中 dispatch() -> action(为了解决接口的复用问题,封装公共的逻辑)-> commit() -> mutation -> 修改状态
47.3 缺点
Vuex 中store只有一份,复杂的数据需要依赖于摸块。Vuex状态是一个树状结构,最终会将模块的状态挂载到根模块上。
- 模块和状态的名字冲爽。
- 数据不够扁平化、调用的时候过长。
- 更改状态mutation和action的选取。
- 模块需要增加namespaced
- 对TS支持并不友好
47.4 原理
对于 Vuex3 核心就是通过 new Vue() 创建了一个Vue实例,进行数据共享。
对于 Vuex4 核心就是通过创建一个响应式对象进行数据共享 reactive()
48. 如何监听 vuex 中数据的变化
- 通过 watch 监控 vuex 中状态变化。
- 通过 store.subscribe 监控状态变化。
49. 页面刷新后 vuex 的数据丢失怎么解决?
- 每次获取数据前检测 Vuex 数据是否存在,不存在则发谓求重新拉取数据,存储到 Vuex 中。
- 采用 Vuex持久化插件,将数据存储到localStraoge 或者sessionStorage 中。
50. mutation 和action 的区别
- 在 action 中可以处理异步逻辑,可以获取数据后将结果提交给 mutation,mutation 中则是修改 state。
- 在 action 中可以多次进行 commit 操作,包括 action 中也可以调用 action。
- 在非 mutation 中修改数据,在严格模式下会发生异常
- dispatch 时会将 action 包装成 promise,而mutation 则没进行包装
51.有使用过 vuex的 module 吗?在什么情况下会使用?
使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
Vuex 允许我们将 store 分割成模块(module)。每个块拥有自己的state、mutation、action、getter、甚至是嵌套子模块。
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = createstore({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
52. Vue3 中 CompositionAPl 的优势是?
- 在Vue2 中采用的是 OptionsAPI,用户提供的 data,props,methods,computed,watch 等属性(用户编写复杂业务逻辑会出现反复横跳问题)
- Vue2 中所有的属性都是通过 this 访问,this 存在指向明确问题。
- Vue2 中很多未使用方法或属性依旧会被打包,并且所有全局API都在 Vue 对象上公开。
- CompositionAPI 对 tree-shaking 更加友好,代码也更容易压缩。
- 组件逻辑共享问题, Vue2 采用 mixins 实现组件之间的逻辑共享;但是会有数据来源不明确,命名冲突等问题。Vue3 采用 CompositionAPI 提取公共逻辑非常方便
- 简单的组件仍然可以采用 OptionsAPI 进行编写,compositionAPI 在复杂的逻辑中有着明显的优势。
53. Vue3 有了解过吗?能说说跟 Vue2 的区别吗?
- Vue3.0 更注重模块上的拆分,在 2.0中无法单独使用部分块。需要引入完整的Vuejs(例如只想使用使用响应式部分,但是需要引入完整的 Vuejs), Vue3 中的模块之间耦合度低,模块可以独立使用。 拆分模块
- Vue2 中很多方法挂载到了实例中导致没有使用也会被打包(还有很多组件也是一样)。通过构建工具Tree-shaking机制实现按需引入,减少用户打包后体积。重写API
- Vue3 允许自定义渲染器,扩展能力强。不会发生以前的事情,改写Vue 源码改造渲染方式。扩展更方便。
- 在 Vue2 的时候使用 defineProperty 来进行数据的劫持,需要对属性进行重写添加 getter 及 setter 性能差。
- 当新增属性和删除属性时无法监控变化。需要通过set、delete 实现
数组不采用 defineProperty 来进行劫持(浪费性能,对所有索引进行劫持会造成性能浪费)需要对数组单独进行处理 - Diff 算法也进行了重写。
- Vue3 模板编译优化,采用 PatchFlags 优化动态节点,采用 BlockTree 进行靶向更新等。
- 相比 Vue2 来说 Vue3 新增了很多新的特性。
54. Vue 项目中的错误如何处理的?
54.1 errorcaptured 钩子
可以捕获来自后代组件的错误,如果全局的config.errorHandler 被定义,所有的错误仍会发送它,因此这些错误仍然会向单一的分析服务的地方进行汇报。
父组件(errorCaptured) -》子组件(errorCaptured) -》孙子组件出错时,错误会一直向上抛。如果errorCaptured 中返回 false 则会阻断传播。
54.2 全局设置错误处理
如果在组件渲染时出现运行错误,错误将会被传递至全局Vue.config.errorHandler 配置函数。
Vue.config.errorHandler = (err,m,info) => {
console.log(err, vm, info):
};
54.3 接口异常处理
instance.interceptors.response.use(
(res)=>{
return res.data;
},
(err) => {
let res = err.response;
if(res.status >= 400){
handleError(response);//统一处理接口异常
}
return Promise.reject(error);
}
);
收集到错误后,提交到前端监控系统中,这样我们可以分析前端代码的异常信息啦。
55. Vue3 准模板编译优化
55.1 PatchFlags优化
Diff算法无法避免新旧虚拟DOM中无用的比较操作,通过patchFlags来标记动态内容,可以实现快速diff算法。
<div>
<h1>Hello Jiang</h1>
<span>{{name}}</span>
</div>
此template经过模板编译会变成以下代码:
import { createElementvNode as _createElementVNode, toDisplaystring as
_toDisplaystring, openBlock as _open8lock, createElementslock as _createElementBlock
} from "vue"
export function render(_ctx,_cache, Sprops, Ssetup, Sdata, Soptions){
return(_openBock(),createElementBlock("div", null,[
_createElementVNode("h1", null,"Hello Jiang"),
_createElementVNode("span",null,_topisplaystring(_ctx.name), 1 /* TEXT */)
]))
}
生成的虚拟DOM是:
{
type: "div",
__v_isVNode: true,
children:[
{type:'h1', props:null, key:null, ...}
{type:symbol(), props:null, key:null, ...}
{type:'span', props:null, key:null, ...}
],
dynamicchildren:[{type:'span', children:ctx.name, patchFlag: 1}]
}
此时生成的虚拟节点多出一个dynamicChildren属性。这个就是block的作用,block可以收集所有后代动态节点。这样后续更新时可以直接跳过静态节点,实现靶向更新
export const enum PatchFlags {
TEXT = 1,// 动态文本节点
CLASS = 1<< 1,// 动志class
STYLE = 1<<2,// 动态style
PROPS = 1<<3,// 除了class\style动态属性
FULL_PROPS = 1<<4,//有key,需要完整diff
HYDRATE_EVENTS = 1<<5,// 挂载过事件的
STABLE_FRAGMENT = 1<<6,// 稳定序列,子节点顺序不会发生变化
KEYED_FRAGMENT = 1<<7,//子节点有key的fragment
UNKEYED_FRAGMENT= 1<<8,//子节点没有key的fragment
NEED_PATCH = 1<9,//进行非props比较,ref比较DYNAMIC SLOTS =1<< 10,// 动态插
DEY ROOT FRAGMENT = 1 << 11,
HOISTED = -1,//表示静态节点,内容变化,不比较儿子
BAIL = -2 // 表示diff算法应该结束
}
55.2 BlockTree
为什么我们还要提出blockTree的概念?只有block不就挺好的么?问题出在block在收集动态节点时是忽略虚拟DOM树层级的。
<div>
<p v-if_"flag">
<span>{{a}}</span>
</p>
<div v-else>
<span>{{a}}</span>
</div>
</div>
这里我们知道默认根节点是一个block节点,如果要是按照之前的套路来搞,这时候切换flag的状态将无法从p标签切换到div标签。解决方案:就是将不稳定的结构也作为block来进行处理
不稳定结构
所谓的不稳结构就是DOM树的结构可能会发生变化。不稳定结构有哪些呢?(v-if/v-for/Fragment)
v-if
<div>
<div v-if_"flag">
<span>{{a}}</span>
</div>
<div y-else>
<p><span>{{a}}</span></p>
</div>
</div>
return function render(_ctx, _cache, Sprops, Ssetup, sdata, Soptions){
return(openBlock(),_createElementBlock("div", null,[
(_ctx.flag)
?(_openBlock(),_createElementBlock("div",{ key:0 },[
_createElementVNode("span",null,_topisplaystring(_ctx.a), 1 /* TEXT */)
]))
:(_openBlock(),createElementBlock("div",{ key: 1 },[
_createElementVNode("p",null,[
_createElementVNode("span",nu11,_topisplaystring(_ctx.a),1 /* TEXT */)
])
]))
]))
}
Block(div)
Blcok(div,{key:0}) -> Block(div,{key:1})
父节点除了会收集动态节点之外,也会收集子block。更新时因key值不同会进行删除重新创建
v-for
随着 v-for
变量的变化也会导致虚拟DOM树变得不稳定
<div>
<div v-for="item in frbits">{{item}}</div>
</div>
export function render(_ctx, _cache, sprops, ssetup, sdata, soptions) {
return(_openBock(true),_createElementBlock(_Fragment, null,
_renderList(_ctx.fruits,(item)=>{
return(_openBlock(),_createElementBlock("div",nu11,_topisplaystring(item), 1/* TEXT */))
}),256 /* UNKEYED_FRAGMENT */))
}
可以试想一下,如果不增加这个block,前后元素不一致是无法做到靶向更新的。因为 dynamicchildren 中还有可能有其他层级的元素。同时这里还生成了一个Fragment,因为前后元素个数不一致,所以称之为不稳定序列。
稳定Fragment
这里是可以靶向更新的,因为稳定则有参照物
<div>
<div v-for="item in 3">{{item}}</div>
</div>
export function render(_ctx, _cache, $props, $setup, sdata, soptions){
return(openBlock(),-createElement6lock("div",null,[
(_openBlock(), _createElementBlock(_Fragment,null,_renderList(3,(item) => {
return _createElementvNode("div", null, _topisplaystring(item), 1 /* TEXT */)
}),64 /* STABLE_FRAGMENT */))
]))
}
54.3 静态提升
<div>
<span>hello</span>
<span a=1 b=2>{{name}}</span>
<a><span>{{age}}</span></a>
</div>
静态提升则是将静态的节点或者属性提升出去。静态提升是以树为单位。也就是说树中节点有动态的不会进行提升。
54.4 预字符串化
静态提升的节点都是静态的,我们可以将提升出来的节点字符串化。当连续静态节点超过20个时,会将
静态节点序列化为字符串。
<div>
<span></span>
...
...
<span></span>
</div>
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<span>...</span>",20)
54.5 缓存函数
<div @click="e=>v=e.target.value"></div>
每次调用render的时都要创建新函数
export function render(_ctx, _cache, Sprops, Ssetup, sdata, Soptions){
return(_openBlock(),_createElementBlock("div",{
onClick: e=>_ctx.v=e.target.value
},null,8 /*PROPS*/,["onclick"]))
}
开启函数缓存后,函数会被缓存起来,后续可以直接使用
export function render(_ctx,_cache, Sprops, Ssetup, sdata, Soptions){
return(_openBlock(),createElementBlock("div",{
onClick:_cache[0] || (_cache[0] = e=>_ctx.v=e.target.value)
}))
}
56. 你知道哪些 Vue3 新特性?
- Composition API
- SFC Composition APl Syntax Sugar (<script setup>)
- Teleport
- Fragments
- Emits Component Option
- createRenderer API from @vue/runtime-core to create custom renderers
- SFC State-driven CSS Variables (v-bind in <style>)
- SFC <style scoped> can now include global rules or rules that target only slotted content
- Suspense experimental
评论 (0)
暂无评论,快来抢沙发吧!