vue.js 设计与实现-vue3源码学习(4)

157 阅读3分钟

前言

前面我们学习了如何实现一个简单的 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.tscreateGettercreateSetter 会触发相应的收集依赖触发依赖。我们在 effect.ts 中定义 tracktiggger 函数

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函数 比如

image.png 此时 name 属性 对应两个 fn ,当触发依赖时,需要同时执行这两个 fn 函数,我们看下对应的数据结构

image.png

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>

image.png

最后

vue3 源码中 reactiveeffect 函数的初始版本我们已经实现了,有兴趣的小伙伴可以将代码拉下来看下gitee.com/Provens/vue… 我们下篇开始学习 ref 函数