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)
相关推荐
NEXT0610 小时前
CSS 选择器深度实战:从“个十百千”权重法到零 DOM 动画的降维打击
前端·css
Mapmost10 小时前
防患未“燃”:掌握森林火灾仿真分析,精准把控火势蔓延趋势
前端
半世轮回半世寻10 小时前
前端开发里最常用的5种本地存储
前端·javascript
OpenTiny社区10 小时前
TinyPro v1.4.0 正式发布:支持 Spring Boot、移动端适配、新增卡片列表和高级表单页面
java·前端·spring boot·后端·开源·opentiny
爱上妖精的尾巴10 小时前
7-9 WPS JS宏 对象使用实例6:按条件读取多表再拆成多表
前端·javascript·wps·jsa
有意义10 小时前
现代 React 路由实践指南
前端·vue.js·react.js
三木檾10 小时前
Cookie 原理详解:Domain / Path / SameSite 一步错,生产环境直接翻车
前端·浏览器
开始学java10 小时前
踩坑实录:把 useRef 写进 JSX 后,我终于分清它和 useState 的核心差异
前端
二DUAN帝10 小时前
像素流与UE通信
前端·javascript·css·ue5·html·ue4·html5
1024小神10 小时前
cloudflare+hono框架实现jwtToken认证,并从token中拿到认证信息
前端