Vue3响应式原理(源码)【reactive,ref,computed】

Vue 3 响应式系统架构详解

1. 响应式系统概述

Vue 3的响应式系统是其核心特性之一,它使得数据变化能够自动触发视图更新。与Vue 2相比,Vue 3的响应式系统基于ES6的Proxy API重构,提供了更好的性能和更丰富的功能。

2. 核心概念

2.1 依赖收集与触发更新

在Vue 3的响应式系统中,有两个核心概念:

  • 依赖收集:当访问响应式数据时,系统会记录哪些副作用函数(effect)依赖于这些数据
  • 触发更新:当响应式数据发生变化时,系统会通知所有依赖该数据的副作用函数重新执行

2.2 副作用函数

副作用函数(effect)是响应式系统中的关键概念,它代表了需要在数据变化时重新执行的代码块。在Vue 3中,通过effect函数来创建副作用函数。

3. 核心模块详解

3.1 reactive模块

reactive函数用于创建响应式对象。它通过Proxy代理原始对象,拦截get和set操作。

javascript 复制代码
const reactive = (obj) => {
  return createReactive(obj)
}

const createReactive = (obj) => {
  if(!isObject(obj)) {
    return obj
  }
  const handlers = {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver)
      track(target, key)
      if(isObject(result)) {
        return reactive(result)
      }
      return result
    },
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver)
      trigger(target, key)
      return result
    }
  }
  const proxy = new Proxy(obj, handlers)
  proxyMap.set(obj, proxy)
  return proxy
}

3.2 ref模块

ref函数用于创建响应式的基本数据类型。它通过包装一个对象来实现响应式。

javascript 复制代码
const ref = (value) => {
  return createRef(value)
}

const createRef = (value, isShallow) => {
  if(isRef(value)) {
    return value
  }
  return new RefImpl(value, isShallow)
}

class RefImpl {
  #__v_isRef = true;
  constructor(value, isShallow) {
    this.#_value = isShallow ? value : toReactive(value)
    this.#__raw_value = value
  }
  get value() {
    trackEffect(this)
    return this.#_value
  }
  set value(newValue) {
    if(newValue === this.#__raw_value) {
      return
    }
    this.#__raw_value = newValue
    this.#_value = toReactive(newValue)
    triggerEffect(this)
  }
}

3.3 computed模块

computed函数用于创建计算属性,它会缓存计算结果并在依赖变化时重新计算。

javascript 复制代码
const computed = (getterOrOptions, debugOptions) => {
  let getter;
  let setter;
  if(isFunction(getterOrOptions)) {
    getter = getterOrOptions;
    setter = () => {
      console.warn('Write operation failed: computed value is readonly');
    }
  } else {
    getter = getterOrOptions.getter;
    setter = getterOrOptions.setter;
  }
  const cRef = new ComputedRefImpl(getter, setter)
  return cRef
}

class ComputedRefImpl {
  #__v_isRef = true;
  is_dirty = true;
  constructor(getter, setter) {
    this._setter = setter
    this.effect = new ReactiveEffect(getter, () => {
      if(!this.is_dirty) {
        this.is_dirty = true;
        triggerEffect(this)
      }
    })
  }
  get value() {
    trackEffect(this)
    if(this.is_dirty) {
      this.is_dirty = false;
      this._value = this.effect.run()
    }
    return this._value
  }
  set value(newValue) {
    this._setter(newValue);
  }
}

4. 关键函数详解

4.1 track函数

track函数负责依赖收集,它记录当前活跃的副作用函数与目标对象属性之间的依赖关系。

javascript 复制代码
const track = (target, key) => {
  if(!activeEffect) {
    return
  } else {
    let depsMap = targetMap.get(target)
    if(!depsMap) {
      depsMap = new Map()
      targetMap.set(target, depsMap)
    }
    let deps = depsMap.get(key);
    if(!deps) {
      deps = new Set()
      depsMap.set(key, deps)
    }
    deps.add(activeEffect)
  }
}

4.2 trigger函数

trigger函数负责触发更新,它通知所有依赖于特定属性的副作用函数重新执行。

javascript 复制代码
const trigger = (target, key) => {
  let depsMap = targetMap.get(target)
  if(!depsMap) {
    return
  }
  let deps = depsMap.get(key)
  if(!deps) {
    return
  }
  const effectsArr = [...deps]
  effectsArr.forEach(effect => {
    effect.run()
  })
}

4.3 effect函数

effect函数用于创建和执行副作用函数。

javascript 复制代码
const effect = (fn) => {
  const _effect = new ReactiveEffect(fn);
  _effect.run()
}

class ReactiveEffect {
  constructor(fn, scheduler) {
    this.fn = fn
    this.scheduler = scheduler
  }
  run() {
    activeEffect = this
    return this.fn()
  }
}

5. 响应式系统架构图

原始对象
Proxy代理
get操作
set操作
track依赖收集
trigger触发更新
依赖关系存储
副作用函数执行
WeakMap存储
视图更新

6. 性能优化要点

Vue 3的响应式系统在性能方面做了多项优化:

  1. 使用WeakMap:避免内存泄漏,当对象被垃圾回收时,相关的依赖关系也会被自动清理
  2. 使用Proxy:相比Vue 2的Object.defineProperty,Proxy可以监听数组变化和对象新增属性
  3. 懒执行:计算属性只有在被访问时才会执行,避免不必要的计算

7. 实际应用示例

javascript 复制代码
const a = ref(5)
const com = computed(() => {
  console.info("cmpted", a.value)
  return a.value * 2
})
setTimeout(() => {
  a.value = 6
  console.info("afterTime", com.value)
}, 1000)
console.info(com.value)

在这个示例中,当a.value发生变化时,计算属性com会自动重新计算并输出新的值。

8. 总结

Vue 3的响应式系统通过Proxy、WeakMap和Effect等机制,实现了高效、灵活的数据响应式处理。理解这些核心概念和实现原理,有助于更好地使用Vue 3进行开发。

9. 完整代码

js 复制代码
// import { isObject } from "lodash"
let proxyMap = new WeakMap()
let targetMap = new WeakMap()
let activeEffect = null

// -------------  reactive  ---------------
class ReactiveEffect {
  constructor(fn, scheduler) {
    this.fn = fn
    this.scheduler = scheduler
  }
  run() {
    activeEffect = this
    return this.fn()
  }
}
const reactive = (obj) => {
  return createReactive(obj)
}
const isObject = (obj) => {
  if(obj === null) {
    return true
  } else if(typeof obj === 'object') {
    return true
  } else {
    return false
  }
}
const createReactive = (obj) => {
  if(!isObject(obj)) {
    return obj
  }
  const handlers = {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver)
      track(target, key)
      if(isObject(result)) {
        return reactive(result)
      }
      return result
    },
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver)
      trigger(target, key)
      return result
    }
  }
  const proxy = new Proxy(obj, handlers)
  proxyMap.set(obj, proxy)
  return proxy
}

const track = (target, key) => {
  // console.info('track-activeEffect', activeEffect)
  if(!activeEffect) {
    return
  } else {
    let depsMap = targetMap.get(target)
    if(!depsMap) {
      depsMap = new Map()
      targetMap.set(target, depsMap)
    }
    let deps = depsMap.get(key);
    if(!deps) {
      deps = new Set()
      depsMap.set(key, deps)
    }
    deps.add(activeEffect)
    // console.info('targetMap', targetMap)
  }
}
const trigger = (target, key) => {
  let depsMap = targetMap.get(target)
  if(!depsMap) {
    return
  }
  let deps = depsMap.get(key)
  if(!deps) {
    return
  }
  const effectsArr = [...deps]
  effectsArr.forEach(effect => {
    effect.run()
  })
}
const effect = (fn) => {
  const _effect = new ReactiveEffect(fn);
  _effect.run()
}

// -------------------------- ref ---------------------------

const isRef = (value) => {
  return value && value.__v_isRef === true
}
const ref = (value) => {
  return createRef(value)
}
const toReactive = (value) => isObject(value) ? reactive(value) : value;
const createRef = (value, isShallow) => {
  if(isRef(value)) {
    return value
  }
  return new RefImpl(value, isShallow)
}
class RefImpl {
  #_value;
  #__raw_value;
  deps;
  __v_isRef = true;
  constructor(value, isShallow) {
    this.#_value = isShallow ? value : toReactive(value)
    this.#__raw_value = value
  }
  get value() {
    trackEffect(this)
    return this.#_value
  }
  set value(newValue) {
    if(newValue === this.#__raw_value) {
      return
    }
    this.#__raw_value = newValue
    this.#_value = toReactive(newValue)
    triggerEffect(this)
  }
}

const trackEffect = (ref) => {
  if(activeEffect) {
    if(!ref.deps) {
      ref.deps = new Set()
    }
    ref.deps.add(activeEffect)
  }
}

const triggerEffect = (ref) => {
  if(ref.deps) {
    let depsArr = [...ref.deps]
    depsArr.forEach(effect => {
      if(effect.scheduler) {
        effect.scheduler()
      } else {
        effect.run()
      }
    })
  }
}

// -------------------------- computed ---------------------------
const isFunction = (value) => {
  return typeof value === 'function'
}
const computed = (getterOrOptions, debugOptions) => {
  let getter;
  let setter;
  if(isFunction(getterOrOptions)) {
    getter = getterOrOptions;
    setter = () => {
      console.warn('Write operation failed: computed value is readonly');
    }
  } else {
    getter = getterOrOptions.getter;
    setter = getterOrOptions.setter;
  }
  const cRef = new ComputedRefImpl(getter, setter)
  return cRef
}

class ComputedRefImpl {
  #__v_isRef
  is_dirty = true;
  deps;
  constructor(getter, setter) {
    this._setter = setter
    this.effect = new ReactiveEffect(getter, () => {
      if(!this.is_dirty) {
        this.is_dirty = true;
        triggerEffect(this)
      }
    })
    this.#__v_isRef = true;
  }
  get value() {
    trackEffect(this)
    if(this.is_dirty) {
      this.is_dirty = false;
      this._value = this.effect.run()
    }
    return this._value
  }
  set value(newValue) {
    this._setter(newValue);
  }
}

const a = ref(5)
const com = computed(() => {
  console.info("cmpted", a.value)
  return a.value * 2
})
setTimeout(() => {
  a.value = 6
  console.info("afterTime", com.value)
}, 1000)
console.info(com.value)

// const a = ref(4)
// effect(() => {
//   console.info("effect-handle", a.value)
// })

// effect(() => {
//   console.info("effect-handle2222", a.value)
// })
// effect(() => {
//   console.info("effect-handle2222334", a.value)
// })
// setTimeout(()=>{
//   a.value = 65
// },1000)
相关推荐
snow@li2 小时前
前端:拖动悬浮小窗
开发语言·前端·javascript
2301_796512522 小时前
ModelEngine平台创建知识库体系 ,帮助“前端职业导航师”定制化私域知识累积
前端·modelengine
鹏程十八少2 小时前
Android ANR项目实战:Reason: Broadcast { act=android.intent.action.TIME_TICK}
android·前端·人工智能
温轻舟2 小时前
圣诞节雪人动态效果 | HTML页面
开发语言·前端·javascript·html·css3·温轻舟·圣诞
云技纵横2 小时前
Vue 2 生产构建 CSS 压缩报错修复与深度选择器规范
前端·css·vue.js
Codebee2 小时前
打破偏见!企业级AI不是玩具!Ooder全栈框架+程序员=专业业务系统神器
前端·全栈
翻斗花园岭第一爆破手2 小时前
flutter3.Container中的decoration
开发语言·前端·javascript
IT_陈寒2 小时前
Java 21虚拟线程实战:7个性能翻倍的异步重构案例与避坑指南
前端·人工智能·后端
锅挤2 小时前
Vue2:小水一下(5)
前端·javascript·html