Vue3响应式原理

关注若川视野,回复"pdf" 领取资料,回复"加群",可加群长期交流学习

本文结构

    - 关于Vue3

    - Vue2响应式原理回顾

    - Vue3响应式方案

    - Vue3响应式原理

    - 手写mini版Vue3响应式

本文共计:2349字2图

预计阅读时间:4min30s

关于Vue3

话说,Vue3已经进行到rc4版本了,4月份beta发布的时候前端圈红红火火

不会吧不会吧,不会你还没开始学吧????

整理了一些资源,现在开始学习应该还不算晚

  • vue-next仓库[1]

  • 20200723 Vue3 官方发布的beta文档[2]

  • Vue3 Roadmap & FAQ[3]

  • Vue3仓库已经合并的780多个PR[4]

  • 尤大在Vue Mastery的Vue3课:Vue 3 Deep Dive with Evan You[5]

  • 202007 尤大在前端会客厅节目关于Vue3的访谈[6]

  • 202005 The process: Making Vue 3[7]

  • 202004 尤大 - 聊聊 Vue.js 3.0 Beta 官方直播[8]

  • 2018 VueConf 杭州 尤大关于Vue3的演讲视频[9]

拉到文章底部找到上述链接,以下正文探讨一下Vue3响应式原理

Vue2 响应式原理回顾

  • 对象响应化:遍历每个key,通过 Object.defineProperty API定义getter,setter

// 伪代码
function observe(){
 if(typeof obj !='object' || obj == null){
  return
 }
 if(Array.isArray(obj)){
  Object.setPrototypeOf(obj,arrayProto)
 }else{
    const keys = Object.keys()
    for(let i=0;i<keys.length;i++){
      const key = keys[i]
      defineReactive(obj,key,obj[key])
    }
 }
}
function defineReactive(target, key, val){
  observe(val)
  Object.defineProperty(obj, key, {
    get(){
      // 依赖收集
      dep.depend()
      return val
    },
    set(newVal){
      if(newVal !== val){
        observe(newVal)
        val = newVal
        // 通知更新
        dep.notify()
      }
    }
  })
}
  • 数组响应化:覆盖数组的原型方法,增加通知变更的逻辑

// 伪代码
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto)
['push','pop','shift','unshift','splice','reverse','sort'].forEach(key=>{
 arrayProto[key] = function(){
  originalProto[key].apply(this.arguments)
  notifyUpdate()
 }
})

vue2响应式痛点

  • 递归,消耗大

  • 新增/删除属性,需要额外实现单独的API

  • 数组,需要额外实现

  • Map Set Class等数据类型,无法响应式

  • 修改语法有限制

vue3响应式方案

使用ES6的 `Proxy`[10] 进行数据响应化,解决上述Vue2所有痛点

Proxy可以在目标对象上加一层拦截/代理,外界对目标对象的操作,都会经过这层拦截

相比 Object.defineProperty ,Proxy支持的对象操作十分全面:get、set、has、deleteProperty、ownKeys、defineProperty......等等

// reactive 伪代码
function reactice(obj){
  return new Proxy(obj,{
    get(target, key, receiver){
      const ret = Reflect.get(target, key, receiver)
      return isObject(ret) ? reactice(ret) : ret
    },
    set(target, key, val, receiver){
      const ret = Reflect.set(target, key, val, receiver)
      return ret
    },
    deleteProperty(target, key){
      const ret = Reflect.deleteProperty(target, key)
      return ret
    },
  })
}

Vue3响应式原理

vue3响应式原理图
  • 通过 effect  声明依赖响应式数据的函数cb ( 例如视图渲染函数render函数),并执行cb函数,执行过程中,会触发响应式数据 getter

  • 在响应式数据 getter中进行 track依赖收集:建立 数据&cb 的映射关系存储于 targetMap

  • 当变更响应式数据时,触发 trigger **,**根据 targetMap 找到关联的cb执行

  • 映射关系 targetMap 结构:

targetMap: WeakMap{ 
 target:Map{ 
  key: Set[cb1,cb2...] 
 }
}

手写vue3响应式

大致结构

// mini-vue3.js

/* 建立响应式数据 */
function reactice(obj){}

/* 声明响应函数cb(依赖响应式数据) */
function effect(cb){}

/* 依赖收集:建立 数据&cb 映射关系 */
function track(target,key){}

/* 触发更新:根据映射关系,执行cb */
function trigger(target,key){}

reactive

/* 建立响应式数据 */
function reactive(obj){
  // Proxy:http://es6.ruanyifeng.com/#docs/proxy
  // Proxy相当于在对象外层加拦截
  // Proxy递归是惰性的,需要添加递归的逻辑
  
  // Reflect:http://es6.ruanyifeng.com/#docs/reflect
  // Reflect:用于执行对象默认操作,更规范、更友好,可以理解成操作对象的合集
  // Proxy和Object的方法Reflect都有对应
  if(!isObject(obj)) return obj
  const observed = new Proxy(obj,{
    get(target, key, receiver){
      const ret = Reflect.get(target, key, receiver)
      console.log('getter '+ret)
      // 跟踪 收集依赖
      track(target, key)
      return reactive(ret)
    },
    set(target, key, val, receiver){
      const ret = Reflect.set(target, key, val, receiver)
      console.log('setter '+key+':'+val + '=>' + ret)
      // 触发更新
      trigger(target, key)
      return ret
    },
    deleteProperty(target, key){
      const ret = Reflect.deleteProperty(target, key)
      console.log('delete '+key+':'+ret)
      // 触发更新
      trigger(target, key)
      return ret
    },
  })
  return observed
}

effect

/* 声明响应函数cb */
const effectStack = []
function effect(cb){

  // 对函数进行高阶封装
  const rxEffect = function(){
    // 1.捕获异常
    // 2.fn出栈入栈
    // 3.执行fn
    try{
      effectStack.push(rxEffect)
      return cb()
    }finally{
      effectStack.pop()
    }
  }

  // 最初要执行一次,进行最初的依赖收集
  rxEffect()

  return rxEffect
}

track

/* 依赖收集:建立 数据&cb 映射关系 */
const targetMap = new WeakMap()
function track(target,key){
  // 存入映射关系
  const effectFn = effectStack[effectStack.length - 1]  // 拿出栈顶函数
  if(effectFn){
    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(effectFn)
  }
}

trigger

/* 触发更新:根据映射关系,执行cb */
function trigger(target, key){
  const depsMap = targetMap.get(target)
  if(depsMap){
    const deps = depsMap.get(key)
    if(deps){
      deps.forEach(effect=>effect())
    }
  }
}

测试demo

<!-- test.html -->
<div id="app">
 {{msg}}
</div>

<script src="./mini-vue3.js"></script>

<script>
  // 定义一个响应式数据
  const state = reactive({
    msg:'message'
  })

  // 定义一个使用到响应式数据的 dom更新函数
 function updateDom(){
  document.getElementById('app').innerText = state.msg
 }

 // 用effect声明更新函数
  effect(updateDom)

  // 定时变更响应式数据
  setInterval(()=>{
    state.msg = 'message' + Math.random()
  },1000)
</script>

效果:

测试效果

如果想获取上述代码,放在了这个仓库:mini-vue3-reactive[11]

参考资料

[1]

vue-next仓库: https://github.com/vuejs/vue-next

[2]

20200723 Vue3 官方发布的beta文档: https://v3.vuejs.org/

[3]

Vue3 Roadmap & FAQ: https://github.com/vuejs/vue/projects/6

[4]

Vue3仓库已经合并的780多个PR: https://github.com/vuejs/vue-next/pulls?q=is%3Apr+is%3Amerged

[5]

尤大在Vue Mastery的Vue3课:Vue 3 Deep Dive with Evan You: https://www.vuemastery.com/courses/vue3-deep-dive-with-evan-you/vue3-overview

[6]

202007 尤大在前端会客厅节目关于Vue3的访谈: https://www.bilibili.com/video/BV1qC4y18721

[7]

202005 The process: Making Vue 3: https://increment.com/frontend/making-vue-3/

[8]

202004 尤大 - 聊聊 Vue.js 3.0 Beta 官方直播: https://www.bilibili.com/video/BV1Tg4y1z7FH

[9]

2018 VueConf 杭州 尤大关于Vue3的演讲视频: https://www.bilibili.com/video/BV1Et41197L4

[10]

Proxy: https://es6.ruanyifeng.com/#docs/proxy

[11]

仓库:mini-vue3-reactive: https://github.com/scarsu/mini-vue3-reactive

- END -

推荐阅读

我在阿里招前端,我该怎么帮你?(文末有福利)
如何拿下阿里巴巴 P6 的前端 Offer
如何准备阿里P6/P7前端面试--项目经历准备篇
大厂面试官常问的亮点,该如何做出?
如何从初级到专家(P4-P7)打破成长瓶颈和有效突破
若川知乎问答:2年前端经验,做的项目没什么技术含量,怎么办?

末尾

你好,我是若川,江湖人称菜如若川,历时一年只写了一个学习源码整体架构系列~(点击蓝字了解我)

  1. 关注若川视野,回复"pdf" 领取优质前端书籍pdf,回复"加群",可加群长期交流学习

  2. 我的博客地址:https://lxchuan12.gitee.io 欢迎收藏

  3. 觉得文章不错,可以点个在看呀^_^另外欢迎留言交流~

小提醒:若川视野公众号面试、源码等文章合集在菜单栏中间【源码精选】按钮,欢迎点击阅读,也可以星标我的公众号,便于查找

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值