一、Vue3.0源码
1、源码组织方式变化
- 采用
TypeScript重写 - 使用Monorepo 管理项目结构
2、Vue3.0的不同构建版本
Vue3.0的构建版本分为4类:cjs、global、browser、bundlercjs:common.js规范组织的代码,包含完整版本的Vue(其中有运行时和编译器)global:可以直接在浏览器中使用,global标识全部,prod标识生产版本(代码被压缩),runtime标识运行时版本browser:可以直接在浏览器中使用,esm标识采用ESmodule的规范bundler:需要配合打包工具使用,vue.runtime.esm-bundler.js是vue的最小版本,只打包使用到的代码
3、项目目录结构变化
4、Composition API设计动机
Vue2.0中的Options API包含描述组件选项(data、methods等)的对象,在描述复杂组件时,同一功能的逻辑被拆分到不同的选项中,随着版本的迭代难以维护Vue3.0中的Compositon API新增一组基于函数的API,可以更加灵活的组织同一个功能的代码片段
5、性能提升
- 响应式系统升级:
Vue2.0是在数据初始化之后,遍历对象的属性通过Object.defineProperty监听属性的变化。vue3.0采用Proxy代理对象拦截对象的访问,可以监听动态新增的属性、删除的属性、数组的索引和length属性。 - 编译优化:
Vue2.0中渲染的最小单位是组件,会标记静态根节点,优化diff的过程,但静态节点还要通过diff。Vue2.0中会标记所有的静态节点,diff的过程只需要对比动态节点内容。Fragments(片段):组件内支持多个同级的根标签- 静态提升:标签中仅仅是纯文本的节点,会提升到
render函数外,再次render无须再次创建 - Patch flag: 标记不同类型的节点(如动态文本节点、有动态属性的节点),优化
diff的过程 - 缓存事件处理函数、组件按需动态导入
- 源码体积优化
Vue3.0中移除了一些不常用API,如:inline-template、filterTree-shaking(依赖ESmodule的模块化规范)
6、Vite的原理浅析
在HTML中以ESmodule的方式引入脚本,执行以下代码:
<script>
window.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded')
})
</script>
<script type="module" src="./modules/index.js"></script>
结论:加载模块并执行是在DOM树创建之后,并且在DOMContentLoaded事件之前执行的。
7、Vite vs Vue-cli的区别
Vite在开发模式下不需要打包就可以直接运行(依赖现代浏览器对ESmodule模块的支持)Vue-cli在开发模式下必须打包后运行Vite支持快速冷启动、按需编译、模块热更新、Vite在生产环境下使用Rollup打包(基于ESmodule的方式打包),Vue-cli使用webpack打包
二、Composition API
1、setup API
// 创建Vue对象
const app = createApp({
// Composition API的入口
setup () {
// 第一个参数 props
// 第二个参数 context,attrs、emit、slots
// 将普通对象转换为响应式对象,并且该对象嵌套的对象也是响应式
const { x, y } = reactive({
x: 0,
y: 0
})
return {
x,
y
}
}
})
app.mount('#app')
return的对象可以使用在模板、methods、生命周期钩子函数中setup的执行时机是props解析完毕,组件实例被创建之前执行的,因此其中通过this获取到组件的实例
2、生命周期钩子函数
3、reactive、toRefs、ref声明响应式对象
toRefs函数要求传入的对象必须是一个代理对象,它可以将一个响应式对象的所有属性转化为响应式数据ref的作用是将普通数据转化为响应式数据,与reactive不同的是ref可以将普通类型的变量转化为响应式对象toRefs可以将reactive()创建出来的响应式对象中的每个属性节点都转化为响应式的
4、computed的用法
5、watch 与 watchEffect
Watch的三个参数- 要监听的数据
- 监听到数据变化后执行的函数,这个函数有两个参数分别是新值和旧值
- 选项对象,
deep(深度监听)和immediate(初始化后立即执行一次)
Watch的返回值- 取消监听的函数
setup () {
const question = ref('')
const answer = ref('')
watch(question, async (newValue, oldValue) => {
const response = await fetch('https://www.yesno.wtf/api')
const data = await response.json()
answer.value = data.answer
})
return {
question,
answer
}
}
watchEffect是watch函数的简化版本,也是用来监听数据的变化。不同的是没有它没有第二个回调函数的参数,返回值是取消监听的函数。watchEffect中的函数初始的时候会首先执行一次,然后监听函数内响应式数据的变化。当stop函数被执行后,会取消监听函数内响应式数据的变化了。
setup () {
const count = ref(0)
const stop = watchEffect(() => {
console.log(count.value)
})
return {
count,
stop,
increase: () => {
count.value++
}
}
}
三、响应式系统原理
1、Vue3 响应式回顾
Proxy对象实现属性监听- 多层属性嵌套,在访问属性过程中处理下一级属性
- 默认监听动态添加的属性
- 默认监听属性的删除操作
- 默认监听数组索引和
length属性 - 可以作为单独的模块使用
2、reactive函数的实现
- 接收一个参数,判断这参数是否是对象
- 创建拦截器对象
handler,设置get、set、deteteProperty - 返回
Proxy对象
3、响应式系统-收集依赖
- 访问响应式对象属性时,去收集依赖(
track)
export function track (target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
dep.add(activeEffect)
}
4、响应式系统-触发更新
- 以
target作为键,找对应的depsMap。找到后以key作为键,找到dep的集合遍历绑定的函数依次执行
- 在设置响应式对象属性时,去触发更新(`trigger`)
export function trigger (target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => {
effect()
})
}
}
5、ref函数的实现
- 接收一个参数可以是原始值或对象,若是对象并且是
ref创建的对象,则直接返回。若是普通对象,则内部会调用reactive
export function ref (raw) {
// 判断 raw 是否是ref 创建的对象,如果是的话直接返回
if (isObject(raw) && raw.__v_isRef) {
return
}
let value = convert(raw)
const r = {
__v_isRef: true,
get value () {
track(r, 'value')
return value
},
set value (newValue) {
if (newValue !== value) {
raw = newValue
value = convert(raw)
trigger(r, 'value')
}
}
}
return r
}
6、对比 reactive和 ref
ref可以把基本数据类型数据,转成响应式对象ref返回的对象,重新赋值成对象也是响应式的reactive返回的对象,重新赋值丢失响应式reactive返回的对象不可以解构
7、computed 函数的实现
computed内部定义一个ref(),使用effect监听传入getter()的变化,将值赋值给result.value并返回
export function computed (getter) {
const result = ref()
effect(() => (result.value = getter()))
return result
}