前言
前面我们学习了如何实现一个简单的 reactive 函数以及打包测试。本篇我们学习 effect 函数
vue3源码学习系列 (1) juejin.cn/post/722481…
vue3源码学习系列 (2) juejin.cn/post/723229…
vue3源码学习系列 (3) juejin.cn/post/723596…
effect
在 effect.ts 中定义 effect 函数进行初始化。我们通过 ReactiveEffect 类来生成我们的 effect 实例,同时执行我们的初始化函数。在 run 方法中我们需要存储当前激活的 activeEffect 我们后面收集依赖和触发依赖时需要用到
export function effect<T=any>(fn:()=>T){
const _effect=new ReactiveEffect(fn)
_effect.run()
}
export let activeEffect:ReactiveEffect | undefined
export class ReactiveEffect<T=any>{
constructor(public fn:()=>T){}
run(){
activeEffect=this
// console.log(this)
return this.fn()
}
}
在 reactivity/src/index 中引入effect
export { effect } from "./effect"
在vue/src/index 中引入我们定义的 effect 函数
export { reactive,effect } from "@vue/reactivity"
回到我们的测试页面中。此时页面初始化时就会执行我们的 effect 函数,而进行页面数据的渲染。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>reactive测试</title>
<script src="../dist/vue.js"></script>
</head>
<body>
<div id="app">
<p id="p1"></p>
</div>
<script>
const {reactive,effect} = Vue
const obj=reactive({
name:'张三'
})
effect(()=>{
document.getElementById("p1").innerText=obj.name
})
</script>
</body>
</html>
此时还无法通过改变数据进行页面的更新,因为我们还未进行代理对象的 get 和 set 进行依赖收集和依赖触发,我们来对这个模块来进行添加。
Handlers
在第三篇中我们的 baseHandlers.ts 中 createGetter 和 createSetter 会触发相应的收集依赖和触发依赖。我们在 effect.ts 中定义 track 和 tiggger 函数
import {track,tiggger} from "./effect"
const get=createGetter()
function createGetter(){
return function get(target:object,key:string|symbol,receiver:object){
const res=Reflect.get(target,key,receiver)
//收集依赖
track(target,key)
return res
}
}
const set=createSetter()
function createSetter(){
return function set(target:object,key:string|symbol,value:unknown,receiver:object){
const res=Reflect.set(target,key,value,receiver)
//触发依赖
tiggger(target,key,value)
return res
}
}
export const mutableHandles:ProxyHandler<object>={
get,
set
}
effect.ts 我们需要对 proxy 对象的属性绑定响应的 fn 函数。而且一个属性可能对应多个fn函数 比如
此时
name 属性 对应两个 fn ,当触发依赖时,需要同时执行这两个 fn 函数,我们看下对应的数据结构
dep.ts 定义 set 数据
import { ReactiveEffect } from "./effect";
export type Dep=Set<ReactiveEffect>
export const createDep=(effects?:ReactiveEffect[])=>{
const dep=new Set<ReactiveEffect>(effects) as Dep
return dep
}
我们来看下依赖收集 track 函数
import { isArray } from "@vue/shared" //判断是否为数组
import {createDep, Dep} from "./dep"
type keyToDepMap=Map<any,Dep>
const targetMap=new WeakMap<any,keyToDepMap>()
//收集依赖
export function track(target:object,key:unknown){
if(!activeEffect) return //我们在初始化时激活的fn
let depsMap=targetMap.get(target)
//target 为{name:'张三'} 为key 查询weakmap,如果无值,我们进行创建
if(!depsMap){
targetMap.set(target,(depsMap=new Map()))
}
//我们根据key 查询我们当前属性是否有fn 函数,如果无值,我们进行创建
let dep=depsMap.get(key)
if(!dep){
depsMap.set(key,(dep=createDep()))
}
//dep 为我们的 Set 数组 ,将fn 添加进数组中
trackEffect(dep)
}
//利用dep 以此跟踪指定key 的effect
export function trackEffect(dep:Dep){
dep.add(activeEffect!)
}
触发依赖也是一样的逻辑
//触发依赖
export function tiggger(target:object,key:unknown,newVal:unknown){
const depsMap=targetMap.get(target)
if(!depsMap) return
const dep:Dep|undefined=depsMap.get(key)
if(!dep) return
triggerEffects(dep)
}
export function triggerEffects(dep:Dep){
const effects=isArray(dep)?dep:[...dep]
//依次触发依赖
for (const effect of effects) {
effect.run()
}
}
进行测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>reactive测试</title>
<script src="../dist/vue.js"></script>
</head>
<body>
<div id="app">
<p id="p1"></p>
<p id="p2"></p>
<button onclick="changeObject()">修改name值</button>
</div>
<script>
const {reactive,effect} = Vue
const obj=reactive({
name:'张三'
})
effect(()=>{
document.getElementById("p1").innerText=obj.name
})
effect(()=>{
document.getElementById("p2").innerText=obj.name
})
const changeObject=()=>{
obj.name="李四"
}
</script>
</body>
</html>
最后
vue3 源码中 reactive 和 effect 函数的初始版本我们已经实现了,有兴趣的小伙伴可以将代码拉下来看下gitee.com/Provens/vue… 我们下篇开始学习 ref 函数