vue3源码解析与前端网络安全
VUE 3 拓展
vue3 对比 vue2
响应式数据
vue2 的响应式数据是通过 Object.defineProperty 进行数据劫持,其存在一些缺点:
- 必须要预知劫持的 key 是什么,并不能很好的监听到对象属性的添加、删除;
- 初始化递归遍历整个 data ,导致深层嵌套数据造成性能负担;
vue3 的响应式数据是通过 Proxy 进行数据劫持的,可以很好的规避 Object.defineProperty 带来的缺陷
组合式 Api
- 代码更利于维护和封装
- Vue2 中,我们会在一个 vue 文件的 data,methods,computed,watch 中定义属性和方法,共同处理页面逻辑 ,一个功能的实现,代码过于分散
- vue3 中,代码是根据逻辑功能来组织的,一个功能的所有 api 会放在一起(高内聚,低耦合),提高可读性和可维护性,基于函数组合的 API 更好的重用逻辑代码
Diff 算法优化
首先了解 vue 的 虚拟dom
虚拟DOM就是通过 JS 去生成一个 AST 节点树
一个dom节点上的属性是非常多的,所以直接操作DOM是非常浪费性能的,解决方案就是可以通过JS的计算性能去换取操作DOM所消耗的性能,既然逃不开操作DOM,那我们可以尽可能少的去操作DOM;
diff 算法
规则一:前序对比
规则二:尾序对比
规则三:新增对比
规则四:卸载移除对比
规则五:乱序对比
vue3.x 中标记和提升所有的静态节点,diff 的时候只需要对比动态节点内容,尽可能复用标记的静态节点。
更好的 TSX 语法支持
// App.tsx
import {ref} from 'vue'
let v = ref<string>('')
const renderDom = ()=>{
return (
<div>
<input v-model = {v.value} type="text" />
<div>{v.value}</div>
</div>
)
}
export default renderDom
<template>
<renderDom></renderDom>
</template>
<script setup lang="ts">
import renderDom from './App'
</script>
ref 全家桶
- ref 将数据包装成响应式数据;
- shallowRef (浅层的ref,一般配合triggerRef强制更新dom);
- triggerRef (手动执行与shallowRef关联的副作用,强制更新视图,ref的视图更新也是因为触发了triggerRef)
- customRef (自定义ref,场景:可以做一些防抖操作)
reative 全家桶
- reactive 接受复杂数据类型,返回响应式数据;
- 数组的赋值两种方式:push解构的值;
- 将数组包装成对象,通过属性赋值;
泛型约束了类型,不能绑定基本数据类型
shallowReactive
to 系列全家桶
- toRef, 如果想让响应式数据和以前的数据关联起来,并且更新响应式数据之后还不想更新视图,那么就可以使用 toRef ;
- toRefs,解构会破坏数据的响应式,通过 toRefs 可以解构出响应式的数据;
源码解析:其实就是把reactive 对象的每一个属性都变成了ref 对象循环 调用了toRef
- toRaw ,将响应式数据中得到原始数据;
应用场景:
A-发起请求前对数据的处理,不希望更新视图,可以先把双向绑定的响应式数据转成原始数据,再进行处理发请求;
B-渲染具有不可变数据源的大列表时,可以跳出响应式的追踪,提高性能;
组件通信 Provide inject
通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,会很麻烦。
场景A:通过路由进行局部组件的刷新,对数据进行操作之后需要手动刷新页面;
场景B:父组件有很多数据需要分发给其子代组件的时候
源码解析:
Inject 接收的值是unknown的问题:
组件实例化的时候注入 provides 的值
自定义Hooks( 区别于vue2的mixins )
钩子主要用来处理复用代码逻辑的封装,在vue2的时候就已经有 Mixins 去做这样的事情了, Mixins 就是将这些多个相同的逻辑抽离出来,各个组件只需要引入 mixins 就能实现一次写代码,多组件受益的效果
缺点是会涉及覆盖问题,同名data、methods、filters覆盖,以及变量来源不明确,不利于阅读,使代码难以维护;
为了解决mixins存在的问题,Vue3.x版本使用了自定义Hooks去做这样的事情。
开源的hooks库(VueUse)
// 定义转换base64的hooks
import { onMounted } from 'vue'
type Options = {
el:string
}
export default function(options:Options):Promise<{baseUrl:string}>{
return new Promise((reslove)=>{
onMounted(()=>{
let img:HTMLImageElement = document.querySelector(options.el) as HTMLImageElement
img.onload = () => {
resolve({
baseUrl: base64(img)
})
}
})
const base64 = (el:HTMLImageElement) => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = el.width
canvas.height = el.height
ctx?.drawImage(el,0,0,canvas.width,canvas.height)
return canvas.toDataURL('image/png')
}
})
}
// 使用 hooks
<template>
<div>
<img id='img' src='./assets/test.png'>
</div>
</template>
<script lang="ts" setup>
import useBase64 from './hooks'
useBase64({el: '#img'}).then(res=>{
window.console.log(res.baseUrl)
})
</script>