【源码系列#02】Vue3响应式原理(Effect)

这篇具有很好参考价值的文章主要介绍了【源码系列#02】Vue3响应式原理(Effect)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

专栏分享:vue2源码专栏,vue3源码专栏,vue router源码专栏,玩具项目专栏,硬核💪推荐🙌
欢迎各位ITer关注点赞收藏🌸🌸🌸

Vue3中响应数据核心是 reactive , reactive 的实现是由 proxy 加 effect 组合,上一章节我们利用 proxy 实现了一个简易版的 reactive,# 【源码系列#01】Vue3响应式原理(Reactive)。接下来让我们一起手写下 effect 的源码

effect

effect 作为 reactive 的核心,主要负责收集依赖,更新依赖

在学习 effect之前,我们再来看下这张图

  • targetMap:存储了每个 "响应性对象属性" 关联的依赖;类型是 WeakMap
  • depsMap:存储了每个属性的依赖;类型是 Map
  • dep:存储了我们的 effects ,一个 effects 集,这些 effect 在值发生变化时重新运行;类型是 Set

【源码系列#02】Vue3响应式原理(Effect)

编写effect函数

// 当前正在执行的effect
export let activeEffect = undefined

export class ReactiveEffect {
  // @issue2
  // 这里表示在实例上新增了parent属性,记录父级effect
  public parent = null
  // 记录effect依赖的属性
  public deps = []
  // 这个effect默认是激活状态
  public active = true

  // 用户传递的参数也会传递到this上 this.fn = fn
  constructor(public fn, public scheduler) {}

  // run就是执行effect
  run() {
    // 这里表示如果是非激活的情况,只需要执行函数,不需要进行依赖收集
    if (!this.active) {
      return this.fn()
    }
    // 这里就要依赖收集了 核心就是将当前的effect 和 稍后渲染的属性关联在一起
    try {
      // 记录父级effect
      this.parent = activeEffect
      activeEffect = this
      // 当稍后调用取值操作的时候 就可以获取到这个全局的activeEffect了
      return this.fn()
    } finally {
      // 还原父级effect
      activeEffect = this.parent
    }
  }
}

export function effect(fn, options: any = {}) {
  // 这里fn可以根据状态变化 重新执行, effect可以嵌套着写
  const _effect = new ReactiveEffect(fn) // 创建响应式的effect
  // issue1
  _effect.run() // 默认先执行一次
}

@issue1 effect 默认会先执行一次

依赖收集

const targetMap = new WeakMap()
export function track(target, type, key) {
  // @issue3
  // 我们只想在我们有activeEffect时运行这段代码
  if (!activeEffect) return 
  
  let depsMap = targetMap.get(target) // 第一次没有
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key) // key -> name / age
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  // 单向指的是 属性记录了effect, 反向记录,应该让effect也记录他被哪些属性收集过,这样做的好处是为了可以清理
  trackEffects(dep)
}

export function trackEffects(dep) {
  if (activeEffect) {
    let shouldTrack = !dep.has(activeEffect) // 去重了
    if (shouldTrack) {
      dep.add(activeEffect)
      // @issue4
      // 存放的是属性对应的set
      activeEffect.deps.push(dep) // 让effect记录住对应的dep, 稍后清理的时候会用到
    }
  }
}

@issue3 当activeEffect有值时,即只在effect运行时执行track依赖收集

@issue4 双向记录 ,一个属性对应多个effect,一个effect对应多个属性

一个属性对应多个 effect: 在之前的 depsMap 图中,我们得知,一个属性映射一个 dep(即 effect 集合,类型为 Set)

一个effect对应多个属性: 在 effect 中,有一个 deps 属性,她记录了此 effect 依赖的每一个属性所对应的 dep。让 effect 记录对应的 dep, 目的是在稍后清理的时候会用到

【源码系列#02】Vue3响应式原理(Effect)

触发更新

export function trigger(target, type, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return // 触发的值不在模板中使用

  let effects = depsMap.get(key) // 找到了属性对应的effect

  // 永远在执行之前 先拷贝一份来执行, 不要关联引用
  if (effects) {
    triggerEffects(effects)
  }
}
export function triggerEffects(effects) {
  effects.forEach(effect => {
    // 我们在执行effect的时候 又要执行自己,那我们需要屏蔽掉,不要无限调用,【避免由activeEffect触发trigger,再次触发当前effect。 activeEffect -> fn -> set -> trigger -> 当前effect】
    // @issue5
    if (effect !== activeEffect) {
      effect.run() // 否则默认刷新视图
    }
  })
}

@issue5 避免由run触发trigger,无限递归循环

我们在执行 effect 的时候,又要执行自己,那我们需要屏蔽掉,不要无限调用【避免由 activeEffect 触发 trigger,再次触发当前 effect。 activeEffect -> fn -> set -> trigger -> 当前effect】

举个栗子

const { effect, reactive } = VueReactivity
const data = { name: '柏成', age: 13, address: { num: 517 } }
const state = reactive(data)
// vue3中的代理都是用proxy来解决的

// 此effect函数默认会先执行一次, 对响应式数据取值(取值的过程中数据会依赖于当前的effect)
effect(() => {
  state.age = Math.random()
  document.getElementById('app').innerHTML = state.name + '今年' + state.age + '岁了'
})

// 稍后name和age变化会重新执行effect函数
setTimeout(() => {
  state.age = 18
}, 1000)

分支切换与cleanup

// 每次执行effect的时候清理一遍依赖,再重新收集,双向清理
function cleanupEffect(effect) {
  // deps 里面装的是name对应的effect, age对应的effect
  const { deps } = effect
  for (let i = 0; i < deps.length; i++) {
    // 解除effect,重新依赖收集
    deps[i].delete(effect)
  }
  effect.deps.length = 0
}

export class ReactiveEffect {
  // @issue3
  // 这里表示在实例上新增了parent属性,记录父级effect
  public parent = null
  // 记录effect依赖的属性
  public deps = []
  // 这个effect默认是激活状态
  public active = true

  // 用户传递的参数也会传递到this上 this.fn = fn
  constructor(public fn, public scheduler) {} // @issue8 - scheduler

  // run就是执行effect
  run() {
    // 这里表示如果是非激活的情况,只需要执行函数,不需要进行依赖收集
    if (!this.active) {
      return this.fn()
    }
    // 这里就要依赖收集了 核心就是将当前的effect 和 稍后渲染的属性关联在一起
    try {
      // 记录父级effect
      this.parent = activeEffect
      activeEffect = this
      // 这里我们需要在执行用户函数之前将之前收集的内容清空
      cleanupEffect(this) // @issue6
      // 当稍后调用取值操作的时候 就可以获取到这个全局的activeEffect了
      return this.fn() // @issue1
    } finally {
      // 还原父级effect
      activeEffect = this.parent
    }
  }
}

export function triggerEffects(effects) {
  // 先拷贝,防止死循环,new Set 后产生一个新的Set
  effects = new Set(effects) // @issue7
  effects.forEach(effect => {
    // 我们在执行effect的时候 又要执行自己,那我们需要屏蔽掉,不要无限调用,【避免由activeEffect触发trigger,再次触发当前effect。 activeEffect -> fn -> set -> trigger -> 当前effect】
    if (effect !== activeEffect) {
      effect.run() // 否则默认刷新视图
    }
  })
}

@issue6 分支切换 - cleanupEffect。我们需要在执行用户函数之前将之前收集的内容清空,双向清理,在渲染时我们要避免副作用函数产生的遗留,举个栗子,我们再次修改name,原则上不应更新页面

每次副作用函数执行时,可以先把它从所有与之关联的依赖集合中删除。当副作用函数执行完毕后,响应式数据会与副作用函数之间建立新的依赖关系,而分支切换后,与副作用函数没有依赖关系的响应式数据则不会再建立依赖,这样副作用函数遗留的问题就解决了;

const { effect, reactive } = VueReactivity
const state = reactive({ flag: true, name: '柏成', age: 24 })

effect(() => {
  // 我们期望的是每次执行effect的时候都可以清理一遍依赖,重新收集
  // 副作用函数 (effect执行渲染了页面)
  console.log('render')
  document.body.innerHTML = state.flag ? state.name : state.age
})

setTimeout(() => {
  state.flag = false
  setTimeout(() => {
    // 修改name,原则上不更新页面
    state.name = '李'
  }, 1000)
}, 1000)

@issue7 分支切换 - 死循环。遍历 set 对象时,先 delete 再 add,会出现死循环

在调用循环遍历 Set 集合时,如果一个值已经被访问过了,但该值被删除,并重新添加到集合,如果此时循环遍历没有结束,那该值会被重新访问

参考资料:ECMAScript Language Specification

【源码系列#02】Vue3响应式原理(Effect)

提示:语言规范说的是forEach时是这样的,实测 for of 遍历Set会有同样的问题。

看一下 triggerEffects 方法,遍历了 effects

export function triggerEffects(effects) {
  effects.forEach(effect => { effect.run() })
}

effect.run 方法中

  • 执行 cleanupEffect(effect),清理一遍依赖
deps[i].delete(effect) 
  • 执行 this.fn(),重新执行函数,重新收集依赖
// track() 方法中
dep.add(activeEffect) // 将副作用函数activeEffect添加到响应式依赖中

解决方法:

let effect = () => {};
let deps = new Set([effect])
deps.forEach(item=>{
  console.log('>>>')
  deps.delete(effect); 
  deps.add(effect)
}); // 这样就导致死循环了

// 解决方案如下,先拷贝一份,遍历的Set对象 和 操作(delete、add)的Set对象不是同一个即可
let effect = () => {};
let deps = new Set([effect])
const newDeps = new Set(deps) 
newDeps.forEach(item=>{
  console.log('>>>')
  deps.delete(effect); 
  deps.add(effect)
}); 

effect嵌套

// 当前正在执行的effect
export let activeEffect = undefined

export class ReactiveEffect {
  // @issue2
  // 这里表示在实例上新增了parent属性,记录父级effect
  public parent = null
  // 记录effect依赖的属性
  public deps = []
  // 这个effect默认是激活状态
  public active = true

  // 用户传递的参数也会传递到this上 this.fn = fn
  constructor(public fn, public scheduler) {}

  // run就是执行effect
  run() {
    // 这里表示如果是非激活的情况,只需要执行函数,不需要进行依赖收集
    if (!this.active) {
      return this.fn()
    }
    // 这里就要依赖收集了 核心就是将当前的effect 和 稍后渲染的属性关联在一起
    try {
      // 记录父级effect
      this.parent = activeEffect
      activeEffect = this
      // 当稍后调用取值操作的时候 就可以获取到这个全局的activeEffect了
      return this.fn()
    } finally {
      // 还原父级effect
      activeEffect = this.parent
    }
  }
}

export function effect(fn, options: any = {}) {
  // 这里fn可以根据状态变化 重新执行, effect可以嵌套着写
  const _effect = new ReactiveEffect(fn) // 创建响应式的effect
  // issue1
  _effect.run() // 默认先执行一次
}

@issue2 利用 parent 解决effect嵌套问题,effect 嵌套的场景在 Vue.js 中常常出现,如:Vue中的渲染函数(render)就是在一个effect中执行的,嵌套组件就会伴随着嵌套 effect

  1. 解决effect嵌套问题----栈方式------------------------vue2/vue3.0初始版本
// 运行effect,此effect入栈,运行完毕,最后一个effect出栈,属性关联栈中的最后一个effect
[e1] -> [e1,e2] -> [e1]
effect(() => {   // activeEffect = e1
  state.name     // name -> e1
  effect(() => { // activeEffect = e2
    state.age    // age -> e2
  })
                 // activeEffect = e1
  state.address  // address = e1
})
  1. 解决effect嵌套问题----树形结构方式----------------vue3后续版本
// 这个执行流程 就类似于一个树形结构
effect(()=>{       // parent = null  activeEffect = e1
  state.name       // name -> e1
  effect(()=>{     // parent = e1  activeEffect = e2
     state.age     // age -> e2
     effect(()=> { // parent = e2  activeEffect = e3
        state.sex  // sex -> e3
     })            // activeEffect = e2
  })               // activeEffect = e1

  state.address    // address -> e1

  effect(()=>{     // parent = e1   activeEffect = e4
    state.age      // age -> e4
  })
})

停止effect和调度执行


export class ReactiveEffect {
  // @issue8 - stop
  stop() {
    if (this.active) {
      this.active = false
      cleanupEffect(this) // 停止effect的收集
    }
  }
}

export function effect(fn, options: any = {}) {
  // 这里fn可以根据状态变化 重新执行, effect可以嵌套着写
  const _effect = new ReactiveEffect(fn, options.scheduler) // 创建响应式的effect @issue8 - scheduler
  _effect.run() // 默认先执行一次

  // @issue8 - stop
  // 绑定this,run方法内的this指向_effect,若不绑定,这样调用run方法时,runner(),则指向undefined
  const runner = _effect.run.bind(_effect)
  // 将effect挂载到runner函数上,调用stop方式时可以这样调用 runner.effect.stop()
  runner.effect = _effect
  return runner
}


export function triggerEffects(effects) {
  // 先拷贝,防止死循环,new Set 后产生一个新的Set
  effects = new Set(effects) // @issue7
  effects.forEach(effect => {
    // 我们在执行effect的时候 又要执行自己,那我们需要屏蔽掉,不要无限调用,【避免由activeEffect触发trigger,再次触发当前effect。 activeEffect -> fn -> set -> trigger -> 当前effect】
    if (effect !== activeEffect) {
      // @issue8 - scheduler
      if (effect.scheduler) {
        effect.scheduler() // 如果用户传入了调度函数,则执行调度函数
      } else {
        effect.run() // 否则默认刷新视图
      }
    }
  })
}

如何使用 stop 和 scheduler ?举个小栗子

  • 当我们调用 runner.effect.stop() 时,就双向清理了 effect 的所有依赖,后续 state.age 发生变化后,将不再重新更新页面
  • 基于 scheduler 调度器,我们可以控制页面更新的周期,下面例子中,会在1秒后,页面由 30 变为 5000
let waiting = false
const { effect, reactive } = VueReactivity
const state = reactive({ flag: true, name: 'jw', age: 30, address: { num: 10 } })
let runner = effect(
  () => {
    // 副作用函数 (effect执行渲染了页面)
    document.body.innerHTML = state.age
  },
  {
    scheduler() {
      // 调度 如何更新自己决定
      console.log('run')
      if (!waiting) {
        waiting = true
        setTimeout(() => {
          runner()
          waiting = false
        }, 1000)
      }
    },
  },
)

// 清理 effect 所有依赖,state.age 发生变化后,将不再重新更新页面
// runner.effect.stop()

state.age = 1000
state.age = 2000
state.age = 3000
state.age = 4000
state.age = 5000

effect.ts

完整代码如下

/**
 * @issue1 effect默认会先执行一次
 * @issue2 activeEffect 只在effect运行时执行track保存
 * @issue3 parent 解决effect嵌套问题
 * @issue4 双向记录  一个属性对应多个effect,一个effect对应多个属性 √
 * @issue5 避免由run触发trigger,递归循环
 * @issue6 分支切换 cleanupEffect
 * @issue7 分支切换 死循环,set循环中,先delete再add,会出现死循环
 * @issue8 自定义调度器 类似Vue3中的effectScope stop 和 scheduler
 */

// 当前正在执行的effect
export let activeEffect = undefined

// @issue6
// 每次执行effect的时候清理一遍依赖,再重新收集,双向清理
function cleanupEffect(effect) {
  // deps 里面装的是name对应的effect, age对应的effect
  const { deps } = effect
  for (let i = 0; i < deps.length; i++) {
    // 解除effect,重新依赖收集
    deps[i].delete(effect)
  }
  effect.deps.length = 0
}

export class ReactiveEffect {
  // @issue3
  // 这里表示在实例上新增了parent属性,记录父级effect
  public parent = null
  // 记录effect依赖的属性
  public deps = []
  // 这个effect默认是激活状态
  public active = true

  // 用户传递的参数也会传递到this上 this.fn = fn
  constructor(public fn, public scheduler) {} // @issue8 - scheduler

  // run就是执行effect
  run() {
    // 这里表示如果是非激活的情况,只需要执行函数,不需要进行依赖收集
    if (!this.active) {
      return this.fn()
    }
    // 这里就要依赖收集了 核心就是将当前的effect 和 稍后渲染的属性关联在一起
    try {
      // 记录父级effect
      this.parent = activeEffect
      activeEffect = this
      // 这里我们需要在执行用户函数之前将之前收集的内容清空
      cleanupEffect(this) // @issue6
      // 当稍后调用取值操作的时候 就可以获取到这个全局的activeEffect了
      return this.fn() // @issue1
    } finally {
      // 还原父级effect
      activeEffect = this.parent
    }
  }
  // @issue8 - stop
  stop() {
    if (this.active) {
      this.active = false
      cleanupEffect(this) // 停止effect的收集
    }
  }
}

export function effect(fn, options: any = {}) {
  // 这里fn可以根据状态变化 重新执行, effect可以嵌套着写
  const _effect = new ReactiveEffect(fn, options.scheduler) // 创建响应式的effect @issue8 - scheduler
  _effect.run() // 默认先执行一次

  // @issue8 - stop
  // 绑定this,run方法内的this指向_effect,若不绑定,这样调用run方法时,runner(),则指向undefined
  const runner = _effect.run.bind(_effect)
  // 将effect挂载到runner函数上,调用stop方式时可以这样调用 runner.effect.stop()
  runner.effect = _effect
  return runner
}

// 对象 某个属性 -》 多个effect
// WeakMap = {对象:Map{name:Set-》effect}}
// {对象:{name:[]}}
// 多对多  一个effect对应多个属性, 一个属性对应多个effect
const targetMap = new WeakMap()
export function track(target, type, key) {
  // 我们只想在我们有activeEffect时运行这段代码
  if (!activeEffect) return // @issue2
  let depsMap = targetMap.get(target) // 第一次没有
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key) // key -> name / age
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  // 单向指的是 属性记录了effect, 反向记录,应该让effect也记录他被哪些属性收集过,这样做的好处是为了可以清理
  trackEffects(dep)
}

export function trackEffects(dep) {
  if (activeEffect) {
    let shouldTrack = !dep.has(activeEffect) // 去重了
    if (shouldTrack) {
      dep.add(activeEffect)
      // @issue4
      // 存放的是属性对应的set
      activeEffect.deps.push(dep) // 让effect记录住对应的dep, 稍后清理的时候会用到
    }
  }
}

export function trigger(target, type, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return // 触发的值不在模板中使用

  let effects = depsMap.get(key) // 找到了属性对应的effect

  // 永远在执行之前 先拷贝一份来执行, 不要关联引用
  if (effects) {
    triggerEffects(effects)
  }
}
export function triggerEffects(effects) {
  // 先拷贝,防止死循环,new Set 后产生一个新的Set
  effects = new Set(effects) // @issue7
  effects.forEach(effect => {
    // 我们在执行effect的时候,有时候会改变属性,那我们需要屏蔽掉,不要无限调用,【避免由activeEffect触发trigger,再次触发当前effect。 activeEffect -> fn -> set -> trigger -> 当前effect】
    // @issue5
    if (effect !== activeEffect) {
      // @issue8 - scheduler
      if (effect.scheduler) {
        effect.scheduler() // 如果用户传入了调度函数,则执行调度函数
      } else {
        effect.run() // 否则默认刷新视图
      }
    }
  })
}

参考资料

Vue3响应式系统实现原理(二) - CherishTheYouth - 博客园文章来源地址https://www.toymoban.com/news/detail-747377.html

到了这里,关于【源码系列#02】Vue3响应式原理(Effect)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • vue3源码(二)reactive&effect

    reactive 方法会将对象变成 proxy 对象, effect 中使用 reactive 对象时会进行依赖收集,稍后属性变化时会重新执行 effec t函数。 1.1 get、set基础实现 如下图,如果采用 target[key] 方法,获取 aliasName 时 不会触发 name 的 get 1.2 完善重复 响应式数据缓存,防止重复代理 使用 map , key

    2024年01月25日
    浏览(46)
  • 【手撕源码】vue3响应式原理解析(文末抽奖)

    🐱 个人主页: 不叫猫先生 🙋‍♂️ 作者简介:2022年度博客之星前端领域TOP 2,前端领域优质作者、阿里云专家博主,专注于前端各领域技术,共同学习共同进步,一起加油呀! 💫优质专栏:vue3从入门到精通、TypeScript从入门到实践 📢 资料领取:前端进阶资料以及文中源

    2024年02月03日
    浏览(61)
  • 【源码系列#03】Vue3计算属性原理(Computed)

    专栏分享:vue2源码专栏,vue3源码专栏,vue router源码专栏,玩具项目专栏,硬核💪推荐🙌 欢迎各位ITer关注点赞收藏🌸🌸🌸 传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象 或者传入一个拥有 get 和 set 函数的对象,创建一个可手动修改的计算状态 @issue1 compute

    2024年02月05日
    浏览(51)
  • 【源码系列#04】Vue3侦听器原理(Watch)

    专栏分享:vue2源码专栏,vue3源码专栏,vue router源码专栏,玩具项目专栏,硬核💪推荐🙌 欢迎各位ITer关注点赞收藏🌸🌸🌸 侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数 第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性

    2024年02月04日
    浏览(62)
  • 前端开发攻略---从源码角度分析Vue3的Propy比Vue2的defineproperty到底好在哪里。一篇文章让你彻底弄懂响应式原理。

    Vue的响应式到底要干什么? 无非就是要知道当你 读取 对象的时候,要知道它读了。要做一些别的事情 无非就是要知道当你 修改 对象的时候,要知道它改了。要做一些别的事情 所以要想一个办法, 把读取和修改的动作变成一个函数 ,读取和修改的时候分别调用对应的函数

    2024年04月17日
    浏览(46)
  • vue3响应式原理

    Vue 3 中的响应式原理是通过使用 ES6 的 Proxy 对象来实现的。在 Vue 3 中,每个组件都有一个响应式代理对象,当组件中的数据发生变化时,代理对象会立即响应并更新视图。 具体来说,当一个组件被创建时,Vue 会为组件的 data 对象创建一个响应式代理对象。这个代理对象可以

    2024年02月15日
    浏览(70)
  • Vue3 数据响应式原理

    核心: 通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等… 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作 Vue3的响应式比Vue2好在哪里? 效率更高了,Vue2中假设监听某个对象,该对象中有一万个属性,他要循

    2024年02月11日
    浏览(50)
  • Vue3响应式原理 私

    响应式的本质:当数据变化后会自动执行某个函数映射到组件,自动触发组件的重新渲染。 响应式的实现方式就是劫持数据,Vue3的reactive就是通过Proxy劫持数据,由于劫持的是整个对象,所以可以检测到任何对象的修改,弥补了2.0的不足。 名词解释: **副作用函数:**函数的

    2024年02月10日
    浏览(38)
  • 手写Vue3响应式数据原理

    我们想要对一个对象数据进行处理,从而实现更改dom。但如何更改对一个对象数据进行更改呢? vue2 的双向数据绑定是利⽤ES5 的⼀个 API ,Object.defineProperty()对数据进⾏劫持 结合 发布订阅模式的⽅式来实现的。 vue3 中使⽤了 ES6 的 ProxyAPI 对数据代理,通过 reactive() 函数给每⼀

    2024年02月11日
    浏览(47)
  • Vue3响应式源码实现

    初始化项目结构 reactive.ts effect.ts 测试 执行 tsc 转成 js 代码,没有 tsc 的全局安装 typescript 新建 index.js ,分别引入 effect.js 和 reactive.js 新建 index.html 然后再根目录执行 安装依赖 然后新建 webpack.config.js 执行命令启动项目

    2024年02月09日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包