下一篇:Vue3.0源码系列(二):响应式原理(实现effect内部的runner和scheduler)
文章背景:此文章是基于mini-vue项目进行,通过学习源码,个人对vue3.0的源码的一点理解和学习的记录,文章中可能有理解不到位或者错误的地方,第一次发掘金,希望大家理解,多多指正哈。
欢迎大家去star和学习哈。 本人github:github.com/zzq921/my-m… 欢迎大家star
一:实现effect
1.首先我们创建一个effect的单元测试,然后根据单元测试实现我们的effect的代码逻辑,我们给effect方法传一个fn方法,方法里面执行对nextAge变量的赋值,我们期待nextAge的值变为11
//1.创建effect的单元测试
describe("effect",()=>{
it('effect逻辑测试',()=>{
const user = reactive{(
{
age:10
}
)}
let nextAge;
effect(()=>{
nextAge = user.age + 1
})
// 单元测试(1)
expect(nextAge).toBe(11)
user.age++
// 单元测试 (2)
expect(nextAge).toBe(12)
})
//2.创建effect方法
export function effect(fn) {
// 创建 ReactiveEffect 类,生成effect工厂,每次生成唯一的effect,每次调用effect的时候,执行fn方法。
return new ReactiveEffect(fn)
}
export class ReactiveEffect{
constructor(fn) {
//接受fn方法
this._fn = fn
}
//每次effect被调用时候,执行run方法,进而执行fn()方法
run() {
this._fn()
}
}
二.实现reactive
2.创建reactive方法和他的单元测试,1.期待包裹前后是不想等的 2.期待对象取值为10
//通过reactive方法包裹,
describe('reactive', () => {
it('happy path',()=>{
const original = {age:10}
const observe = reactive(original)
expect(observe).not.toBe(original)
expect(observe.age).toBe(10)
})
})
//reactive实质就是通过Proxy实现对象数据的响应式监听,get方法做依赖收集,set方法做依赖触发
function reactive(raw: any) {
//
return new Proxy(raw, {
get(target,key) {
const res = Reflect.get(target,key)
//这里需要做依赖收集track方法
//track(target,key)
return res
},
set(target,key,value) {
const res = Reflect.get(target,key,value)
//这里做依赖的触发
//trigger(target,key,value)
return res
}
})
}
通过reactive方法的实现,我们就通过了reactive的单元测试,这样我们就实现了简单的reactive方法,就可以把方法在effect的单元测试中进行引用。
3.我们在ReactiveEffect类中抽离出run方法,来执行用户传过来的fn方法。这是我们就实现了 effect单元测试(1)的方法,
export function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.fn()
}
export class ReactiveEffect{
constructor(fn) {
//接受fn方法
this._fn = fn
}
//每次effect被调用时候,执行run方法,进而执行fn()方法
run() {
return this._fn()
}
}
4.接下来我们在reactive中进行依赖收集track函数。
let activeEffect; // 当前effect
export function track(target,key) {
//targetMap -- depsMap -- dep
//通过目标对象的target获取 depsMap对象,此对象为map结构 key-dep
let depsMap = targetMap.get(target)
//初始化时候depsMap不一定存在,所以要进行判断
if(!depsMap) {
//不存在初始化一个depsMap对象,
depsMap = new Map()
//设置目标target为key,新建的depsMap为value,targetMap为存储map容器
targetMap.set(target,depsMap)
}
//当depsMap存在时候,通过key取到value对象
let dep = depsMap.get(key)
//如果dep容器不存在或者没有取到值
if(!dep) {
//新建一个dep对象,此对象结构为set结构。
dep = new Set()
// 属性值作为key,effect的集合dep作伪value,存储到depsMap的集合中。
depsMap.set(key,dep)
}
//如果dep存在,把dep当作参数传入,进行依赖收集。封装成一个trackEffects方法,方便后续进行复用。
trackEffects(dep)
}
export function trackEffects(dep) {
//此dep为所有与key有关的effect集合,收集到set结构的dep中。
//dep = [effect,effect,effect,effect]
if(dep.has(activeEffect)) return
dep.add(activeEffect)
}
5.最后我们在接下来我们在reactive中进行依赖收集trigger函数
export function trigger(target,key) {
//通过目标值target,依赖的key值,获取所有与此依赖有关的集合dep
let depsMap = targetMap.get(target)
let dep = depsMap.get(key)
//触发依赖的方法,把dep当作参数传入,封装成一个triggerEffects方法,方便后续进行复用
triggerEffects(dep)
}
export function triggerEffects(dep) {
//循环set结构的dep数组
for(let effect of dep) {
//取出每一个effect,直接进行依赖触发,执行run方法,run方法里会调用fn
effect.run()
}
}
通过依赖收集track和触发依赖的trigger方法,我们就完成了单元测试 (2)的单元测试,
总结:vue3.0的响应式其实就是通过reactive方法中的new proxy(),对数据进行监听,通过get方法进行依赖收集,把所有依赖的effect放到一个容器 dep中。当依赖变化时,触发set,执行trigger方法,遍历dep,取出所有effect来进行执行。说的可能有欠缺的地方,第一次写掘金,不喜勿喷哈。