2024-04-25 vue3 ref reactive 源码响应式实现思路

响应式的实现

1:reactive

shallowReadonly:只读属性,只对第一层代理,不可以修改第一层属性值,一般不用

shallowReactive: 只对第一层代理,可以修改第一层的属性值,一般不用

reactive:对所有属性,包括深层次的属性都进行代理,全部可以修改。

readonly:对所有属性进行代理,包括深层次的属性,全都不可以修改。性能优化

使用proxy代理数据源,在get中进行依赖收集,在set中触发渲染。如果是只读,就不支持set,如果是shallow,只代理第一层属性。

vue2中的代理是直接递归全都代理,而在3中是懒代理,只有使用了才会去走代理。3中代理实现的方式是proxy。

在代理的时候,vue会对已经代理的数据缓存进weakmap,当新数据进行代理,会先从weakmap找,找不到才会进行代理。

2:依赖收集 effect

effect函数只要是用于依赖收集,具体流程是,effect接收一个函数,在函数执行的时候如果使用reactive或者ref变量,就会触发该变量的get方法,在get方法中会将当前活跃的effect放到全局变量 targetMap中。

effect可能会套effect

vue的处理方法是将每个effect放到一个栈中,当前的effect执行完毕后,将当前的effect从栈中剔出,然后将上一个effect设置为活跃的effect。先进后出

const a = reactive({
b: 1,
c: 2
})
effect(() => {
a.b
effect(() => {
 a.c
})
})

在一个effect中调用多次同一个响应式变量,只收集一次。

const a = reactive({
  b: 1
})
effect(() => {
  a.b
  a.b
})

在effect栈中,保证只存一个相同的effect,解决办法在往数组中添加时做下判断

每一个属性的依赖收集中保证只收集一次的方法:在vue中有一个全局变量targetMap,这个map存储着所有的响应式属性的依赖收集,具体结构是

targetMap = {a: {
  b: {effect: effect._trackId} 
// 这个map存储着引用b的effect,在给b的map添加effect时,
//会根据effect的_trackId判断是否已经存在。主要用于过滤重复收集
}}
3:get依赖收集

如果是对象,直接在get方法中将effect放入targetMap中即可。
如果是数组的方法,会进行特殊处理。
具体处理逻辑如下:

['includes', 'indexOf', 'lastIndexOf']

如果是 这三个方法,会对数组的每一个属性进行依赖收集

  ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      const arr = toRaw(this) as any
      for (let i = 0, l = this.length; i < l; i++) {
        track(arr, TrackOpTypes.GET, i + '')
      }
      // we run the method using the original args first (which may be reactive)
      const res = arr[key](...args)
      if (res === -1 || res === false) {
        // if that didn't work, run it again using raw values.
        return arr[key](...args.map(toRaw))
      } else {
        return res
      }
    }
  })
['push', 'pop', 'shift', 'unshift', 'splice']

如果是 这几个方法,会直接修改数组,接着返回,不进行依赖收集。注意:这几个方法虽然是改变数据源,但是走的是get,而不是set。

  ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      pauseTracking()
      pauseScheduling()
      const res = (toRaw(this) as any)[key].apply(this, args)
      resetScheduling()
      resetTracking()
      return res
    }
  })
length

如果是length,走正常的收集逻辑

下标

如果是直接通过下标获取,则进行对象逻辑处理。走正常的收集逻辑。在targetMap中,数组的下标就是key,effect就是value

3:set触发更新

在修改响应式属性的值的时候,会触发代理的set方法,在这个方法中会先判断是新增还是修改,如果是修改会先看老值和新值是否相等,接着就是根据key获取targetMap中的相对应的effect对象,遍历执行相对应的effect函数即可。
如果是直接修改数据的长度,这里的处理是获取使用length对应的effect,以及大于数组新长度的effect。
源码处理逻辑:

if (key === 'length' && isArray(target)) {
    const newLength = Number(newValue)
    depsMap.forEach((dep, key) => {
      if (key === 'length' || (!isSymbol(key) && key >= newLength)) {
        deps.push(dep)
      }
    })
  }
4: ref实现

ref内部实现了一个 RefImpl class类,借助于get和set实现的响应式收集和触发更新,本质还是和vue2一样借助于object.defineproperty实现的。
这块要注意的是ref在内部会做下判断,如果接收的是一个对象会走reactive函数,实现响应式。reactive的返回值会赋给 RefImpl的_value属性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354