Vue3 ref 和 reactive 区别,如何选择?
个人认为 ref 和 reactive 其实 没必要这个都抛出来给我们用,容易造成一些使用困扰,ref我觉就好,不过存在即合理,还是细说一下区别
区别
ref() | reactive() |
---|---|
✅支持基本数据类型+引用数据类型 | ❌只支持对象和数组(引用数据类型) |
❌在 | ✅在 |
✅重新分配一个新对象不会失去响应 | ❌重新分配一个新对象会丢失响应性 |
需要使用 .value 访问属性 | 能直接访问属性 |
✅传入函数时,不会失去响应 | ❌将对象传入函数时,失去响应 |
✅解构对象时会丢失响应性,需使用toRefs | ❌解构时会丢失响应性,需使用toRefs |
Vue 模板编译的原理
响应式是 Vue中很重要的一环,但是模板编译也是很重要的一环,从面试的角度来说,Vue的模板编译主要是这几个步骤:
- 解析:解析器将模板解析为抽象语树 AST,只有将模板解析成 AST 后,才能基于它做优化或者生成代码字符串
-
- 使用正则等方式解析模板字符串,生成 AST 抽象语法树
- 遍历 AST,生成渲染函数
- 优化:优化抽象语法树
-
- 检测子节点中是否是纯静态节点
- 对数据访问点进行转换,生成 getter/setter,实现响应式
- 使用缓存存放已经编译好的渲染函数,避免重复编译
- 生成:将渲染函数打包生成新函数,返回函数的字符串形式
-
- 依赖响应式系统触发更新,执行渲染函数重新渲染
- 通过 diff 算法对比新旧节点,最小化更新实际 DOM
Vue、React、Angular 模板编译方式优缺点
框架 | 模板语法 | 编译方式 | 学习曲线 |
---|---|---|---|
Vue.js | 简单 HTML-like | 运行时和构建时编译 | 低 |
易于理解 | |||
React | JSX (JavaScript XML) | 编译为 JavaScript | 中等 |
嵌入 JavaScript 中 | |||
Angular | 复杂,基于 HTML | 预编译 (Ahead of Time) | 较高 |
双向数据绑定 |
Vue diff 算法的过程
Vue2
关于Vue2 的 diff 算法个人的理解上是:
- 深度优先+双指针(头尾交叉对比)的 diff
-
- 在对比其子节点数组时,vue对每个子节点数组使用了两个指针,分别指向头尾,然后不断向中间靠拢来进行对比,
- 这样做的目的是尽量复用真实dom,尽量少的销毁和创建真实dom
- 之后的节点对比就是深度优先对比的步骤
- 逐层比较新旧虚拟 DOM 树的节点,对于每一层,它会按照顺序比较节点,找出差异,并标记需要更新的地方。这样的遍历方式有助于更快地发现差异,因为它会首先比较同一层级的节点,然后再递归到下一层级。
- 通过虚拟节点的key和tag来进行判断是否相同节点
- 如果相同则将旧节点关联的真实dom的挂到新节点上,然后根据需要更新属性到真实dom,然后再对比其子节点数组
- 如果不相同,则按照新节点的信息递归创建所有真实dom,同时挂到对应虚拟节点上,然后移除掉旧的dom
- 深度优先(同层比较):
- 双指针:
这样一直递归的遍历下去,直到整棵树完成对比。
流程图
### Vu3
- 双指针+最长递增子序列 diff算法
-
- 把没有比较过的新的vnode节点,建立一个数组,每个子元素都是 0 里面的数字记录老节点的索引 ,数组索引就是新节点的索引
- 如果找到与当前老节点对应的新节点那么 ,将新节点的索引,赋值给newIndex
- 没有找到与老节点对应的新节点,卸载当前老节点
- 如果找到与老节点对应的新节点,把老节点的索引,记录在存放新节点的数组中
- map = [0,0,0,0]
- 对于老的节点大于新的节点的情况 , 对于超出的节点全部卸载 ( 这种情况说明已经patch完相同的vnode )
- 如果新的节点大于老的节点数 ,对于剩下的节点全部以新的vnode处理( 这种情况说明已经patch完相同的vnode )。
- 如果第一步没有patch完,立即,从后往前开始patch ,如果发现不同立即跳出循环
- 从头对比找到有相同的节点 patch ,发现不同,立即跳出
- 预处理前前置节点
- 预处理后置节点
- 处理仅有新增节点的情况
- 处理仅有删除节点的情况
- 处理新增、删除、移动混合的情况
-
- 如果节点发生移动 记录已经移动了
- patch新老节点 找到新的节点进行patch节点
Vue 常见的性能优化方式(结合项目场景)
在实际项目中,Vue 的性能优化需要根据具体的场景和需求来选择合适的策略。以下是一些常见的 Vue 性能优化方式,结合项目场景进行总结:
- 使用生产环境构建:
-
- 在生产环境中使用 Vue 的生产版本,以减少体积和提高性能。
- 异步组件和路由懒加载:
-
- 对于大型项目,使用异步组件和路由懒加载,以分割代码并实现按需加载,减小初始加载体积。
- 合理使用 v-if 和 v-show:
-
- 对于频繁切换的元素,使用 v-show,对于不经常切换的元素,使用 v-if,以减少 DOM 元素的挂载和卸载。
- 合理使用 v-for:
-
- 遍历大数据集时,避免在模板中访问复杂度较高的属性,最好在数据源中进行预处理。如果数据不变,可以考虑使用 Object.freeze 冻结对象,以防止 Vue 的响应式系统监听它。
- 合理使用计算属性和 Watch:
-
- 将复杂的计算逻辑放入计算属性,避免在模板中进行复杂的计算。使用 deep 选项和 immediate 选项来优化 Watcher。
- 合理使用事件委托:
-
- 在父组件上使用事件委托,将事件处理推移到父组件上,以减少子组件的监听器数量。
- 合理使用 keep-alive:
-
- 对于频繁切换的组件,可以考虑使用 缓存组件实例,以减少组件的销毁和重新创建。
- 合理使用缓存
-
- 利用缓存机制,例如在数据请求结果中使用缓存,以避免不必要的重复请求。
- 合理使用过渡效果和动画:
-
- 控制过渡效果和动画的触发时机,避免在大量元素上使用过渡效果,以提高性能。
- 优化网络请求 - 使用合适的数据加载方式,例如分页加载或滚动加载,以降低页面初始化时的请求量。
- 性能监控和分析 - 使用工具进行性能监控和分析,例如 Chrome DevTools、Vue DevTools 等,及时发现和解决性能问题
- 使用 Object.freeze() 进行不需要进行响应式的数据进行优化(vue2)