Vue3源码解读(1)

94 阅读4分钟

Vue3 源码的项目文件解读

image.png

  • @/vue/compiler-sfc(*) 核心 用于解析.vue

    • 依赖 compiler-dom 核心
    • 和 compiler-core 核心
  • @vue/reactivity 核心: 响应式系统,配合下面两个模块工作(桥梁)

  • @vue/runtime-dom 核心: 对于dom的一些操作和解析

  • @vue/runtime-core 核心: 对于生命周期,事件处理,组件的处理

核心模块 reactivity

让数据变成响应式的proxy数据

里面核心的功能代码

  • reactive: 定义响应式数据
  • effect: 副作用函数,用于数据更新后,让副作用重新执行。 组件更新后的视图更新,watch,以及computed在依赖更新后的视图更新,都是基于effect来实现的。这里的effect 就相当于vue2 里面的watcher

【effect, 副作用函数,第一次的时候会先执行一次,后续里面用到的响应式数据变化,会重新执行!!!】

 import {effect} from 'vue' 
 
 const state = reactive({age:30})
 
 effect(() => {
     console.log('age:'+ state.age)
 })
 // 页面加载执行一次,一秒后age变化,再执行一次!!effect传入的回调!
 settimeout(() => {state.age ++}, 1000)

【watcher:观测数据变化的函数[类]】

【引出一个概念,vue2 的响应式系统是基于观察者模式的,在遍历每个属性,将属性劫持变成响应式属性的时候,每个属性都有一个Dep实例,记录了所有的依赖该响应式数数据的watcher函数,当数据变化,dep就会通知所有依赖他的watcher去调用更新方法。也就是说 观察者watcher需要被Dep目标对象所收集,目的是在数据变化的时候,可以notify所有的watcher去更新视图】

【再引出一个概念,设计模式: 发布订阅,观察者模式,其中发布订阅模式最典型的就是Vue中的事件总线eventBus, 观察者模式的典型案例就是vue2的响应式系统, 发布订阅以及观察者的区别在于观察者模式比订阅发布少了一个事件中心!!!】

reactive.ts   简易版本的reactive

import isObject from "@vue/shared"

const proxyMap = new WeakMap()
// 用于缓存已经代理过的对象,避免重复代理
enum isReactiveObj {
  IS_PROXY = '__v_isProxy'
}

const proxyHandler: ProxyHandler<any> = {
  get(target, key, receiver) {
    if (key === isReactiveObj.IS_PROXY) {
      return true
    }
      return Reflect.get(target, key, receiver)
  },
  set(target, key, newVal, receiver) {
      // return target[key] = newVal
    return  Reflect.set(target, key, newVal, receiver)
  }
}

export function reactive(target) {
  return createReactiveObject(target)
}

function createReactiveObject(target) {
  if (!isObject(target)) {
    return target
    // 如果不是对象,直接返回原值
  }
  if(target[isReactiveObj.IS_PROXY]){
    return  target
  }
  if (proxyMap.get(target)) {
    // 如果已经被代理过一次,那么直接返回前一次的代理
    return proxyMap.get(target)
  }
  let proxy = new Proxy(target, proxyHandler)
  // 将传进来的对象用proxy拦截并代理
  proxyMap.set(target, proxy)
  // 缓存代理对象
  return proxy
}

Reflect 的使用


注意到有这样一个场景【对象里有一个访问器】

 const ob = {
    name: 'js',
    get aliasName() {
      return this.name + '-ts'
    }
  }
  const obp = new Proxy(ob, {
    get(target, key, receiver) {
      console.log(key,'666')
      // return target[key]** 壹 **
      return Reflect.get(target,key,receiver) ** 贰 **
    }
  })

  console.log(obp.aliasName)

【如果以第一种方式定义get的返回值,通过代理对象的aliasName, 是可以打印出 js-ts, 但是这里会有一个很明显的问题就是 aliasName 中有name的使用,但是并没有把name属性给代理到,访问obp.aliasName,打印的key也只有aliasName。访问person(this).name 不会触发get。】

【但是如果使用第二种方式,reflect,访问aliasName,并且aliasName中也有对name的访问,那么 aliasName 以及name 这两次的属性访问都会触发代理对象的get】

【所以 回到上面的手写reative.ts中,需要把handler中的get,set的返回值都改成Relefct.get()/set()】
effect.js  的简易逻辑

export let activeEffect
// 导出全局的变量,让reactive.ts reactiveEffect ,trigger.js  等文件可以拿到当前运行的effect
 
// 创建一个响应式
export function effect(fn, options) {
  const _effect = new ReactiveEffect(fn, () => {
    _effect.run()

  })
  _effect.run()
  //effect 就是第一次会执行一次。 后续 effect 里面的响应式数据变化,就会重新执行fn
}

// 加上public 关键字的,就可以属性默认绑到this上
// fn:用户逻辑  scheduler:调度函数【依赖变化,重新调用scheduler 】
class ReactiveEffect {
  public active = true;
  constructor(public fn, public scheduler) { }

  run() {
    if(!this.active){
      return this.fn()

    }
    let lastEffect = activeEffect
    // 这里这样来回赋值是为了解决effect 里面嵌套了effect的情况,就是将外层的effect保存一下,等里面的effect执行完,再把最外层的effect赋值回去
    // 如果effect 是激活的,那么就需要收集依赖
   try{
    activeEffect = this // 将当前的effect 给到全局的activeEffect
    this.fn()
   } 
   finally {
    activeEffect = lastEffect
    // 最后将最外层的effect 赋值为当前的effect
   }
  }
}