vue3源码解析(一)
vue3源码解析
简介
起因:一个遇到的问题--使用reactive定义数组对象类型,传[]时无法触发响应式 解决方案: 1. 可以使用对象包裹数组,使其成为对象的一个属性,就不影响后续的赋值2. 改为ref定义3. 使用数组的push方法,不使用=直接赋值,使用push
这个问题引发了思考,为什么reactive
定义的需要这样处理,它的内部是如何实现的?
带着这些问题,我clone了vue的源码去查看
模块划分
如上图所示,按照模块可以分为三个部分,Reactivity Module/Complier Module/Renderer Module,分别对应的是响应式处理、编译、渲染。
本次重点在于Reactivity Module
中的reactive
的实现
几个概念-有助于理解源码
weakMap
mdn上对于weakMap
的解释是
对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意。它的键被弱保持,也就是说,当其键所指对象没有其他地方引用的时候,它会被 GC 回收掉。WeakMap提供的接口与Map相同。
总结来说就是:
- 只接受对象作为键名
- 键名所引用的对象是弱引用
reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler (en-US) 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。
是一个内置对象,可简化的创建 Proxy 可以将操作转发到原始对象
proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
let proxy = new Proxy(target, {})
响应式--依赖收集和派发更新
举一个简单的发布-订阅模式的例子
比如小明最近在淘宝网上看上一双鞋子,但是联系到卖家后,才发现这双鞋卖光了。下单的按钮变成了到货提醒,于是小明点击了到货提醒的按钮。但与此同时,小王,小张等也喜欢这双鞋,也点击了到货提醒的按钮;等来货的时候就依次会通知他们;
在上面的故事中,可以看出是一个典型的发布订阅模式,卖家是属于发布者,小王,小明等属于订阅者,订阅该店铺,卖家作为发布者,当鞋子到了的时候,会依次通知小明,小王等,依次使用旺旺等工具给他们发布消息;
带入到代码中要实现响应性,就需要在合适的时机,依赖收集(track),触发改变时再次执行副作用 effect-派发更新(trigger)
以product
为例,targetMap用于存储每个响应式的依赖(product),depsMap用于存储每个属性的依赖(price、quantity),dep是effect集。
我们尝试手写响应式模型
一个最基本的响应式
let targetMap = new WeakMap()
function track(target,key){
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(effect)
}
function trigger(target,key){
let depsMap = targetMap.get(target)
if(!depsMap) return
let dep = depsMap.get(key)
if(dep){
dep.forEach(effect=>{effect()})
}
}
let product = {
price: 3,
quantity: 2
}
let total = 0
var effect = ()=>{
total = product.price * product.quantity
}
track(product, 'price')
effect()
console.log("🚀 ~ file: index.js:60 ~ effect ~ total:", total)
product.price = 5
trigger(product, 'price')
console.log("🚀 ~ file: index.js:63 ~ effect ~ total:", total)
打印的结果是
🚀 ~ file: index.js:36 ~ effect ~ total: 6
🚀 ~ file: index.js:36 ~ effect ~ total: 10
这里需要手动的去收集依赖(track),手动触发trigger,可以优化为自动的执行
优化后的
let targetMap = new WeakMap()
function track(target,key){
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(effect)
}
function trigger(target,key){
let depsMap = targetMap.get(target)
if(!depsMap) return
let dep = depsMap.get(key)
if(dep){
dep.forEach(effect=>{effect()})
}
}
function reactive(target) {
const handlers = {
get(target, key, receiver) {
let result = Reflect.get(target, key, receiver)
track(target, key)
return result
},
set(target, key, value, receiver) {
let oldValue = target[key]
let result = Reflect.set(target, key, value, receiver)
if (result && oldValue != value) {
trigger(target, key)
}
return result
},
}
return new Proxy(target, handlers)
}
let product = reactive({
price: 3,
quantity: 2
})
let total = 0
var effect = ()=>{
total = product.price * product.quantity
}
effect()
console.log("🚀 ~ file: index.js:54 ~ effect ~ total:", total)
product.price = 5
console.log("🚀 ~ file: index.js:56 ~ effect ~ total:", total)
打印的结果是
🚀 ~ file: index.js:54 ~ effect ~ total: 6
🚀 ~ file: index.js:56 ~ effect ~ total: 10
这里发现,effect还不能自动收集,需要做成灵活的
继续优化effect
const targetMap = new WeakMap()
let activeEffect = null
function track(target, key){
if(activeEffect){
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)
}
}
function trigger(target,key){
let depsMap = targetMap.get(target)
if(!depsMap) return
let dep = depsMap.get(key)
if(dep){
dep.forEach(effect=>{effect()})
}
}
function reactive(target) {
const handlers = {
get(target, key, receiver) {
let result = Reflect.get(target, key, receiver)
track(target, key)
return result
},
set(target, key, value, receiver) {
let oldValue = target[key]
let result = Reflect.set(target, key, value, receiver)
if (result && oldValue != value) {
trigger(target, key)
}
return result
},
}
return new Proxy(target, handlers)
}
function effect(eff) {
activeEffect = eff
activeEffect()
activeEffect = null
}
let product = reactive({
price: 5,
quantity: 2
})
let total = 0
let salePrice = 0
effect(() => {
total = product.price * product.quantity
})
effect(() => {
salePrice = product.price * 0.9
})
console.log(
`Before updated quantity total (should be 10) = ${total} salePrice (should be 4.5) = ${salePrice}`
)
product.quantity = 3
console.log(
`After updated quantity total (should be 15) = ${total} salePrice (should be 4.5) = ${salePrice}`
)
product.price = 10
console.log(
`After updated price total (should be 30) = ${total} salePrice (should be 9) = ${salePrice}`
)
打印的结果是
Before updated quantity total (should be 10) = 10 salePrice (should be 4.5) = 4.5
After updated quantity total (should be 15) = 15 salePrice (should be 4.5) = 4.5
After updated price total (should be 30) = 30 salePrice (should be 9) = 9
一个最基本的reactive就已经实现了,当然还有很多场景和细节需要处理,这里用到的变量和方法都和vue3中的保持一致。
感兴趣的可以去看源码部分一起交流!
转载自:https://juejin.cn/post/7233625509107040315