简易实现vue3的响应式

467 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

前言

想了解Vue3的响应式是如何实现的嘛? 下面我带着大家去实现一个简易的响应式代码. 帮助大家理解Vue3响应式的原理. ok, 话不多说,下面跟着我的思路一起去看看吧

正文

响应式具体要实现的是一个什么功能呢? 那么我们可以先去找一个场景需求, 相信古惑仔应该是大部分人的童年回忆了,其中有一部只手遮天中,浩南和乌鸦竞拍一条长红,浩南出多少价,乌鸦使用比浩南多100块. 那么如果这个用代码怎么实现呢?

image.png

var haoNan = 180000 // 浩南出价18万
var wuya = haoNan + 100 // 始终比浩南多100块

看,很简单呀. 但是如果浩南又出19万,20万呢, 那么我们在浩南叫了价后,又得给乌鸦的叫价重新赋值. 怎么才能做到我设定一个规则, 规则就是比浩南多100块, 只要浩南重新报过了价, 那么我的价格也会重新报过,并且始终就比浩南多100块. 这就要用到数据响应式了. 我们用Vue3写的话,可以这样写:

import { ref, effect } from 'vue'

let haoNan = ref(180000)
let wuYa = 0

effect(() => {
  wuYa = haoNan.value + 100
  console.log(wuYa)
})

haoNan.value = 190000
// 180100  // 乌鸦报价
// 190100  // 乌鸦报价

ref实现

那么如果我们自己实现的话, 需要怎么做呢? 首先上面的代码,我们应该先有一个ref响应式对象.

接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property .value

class RefImpl {
  constructor (value) {
    this._value = value
  }

  get value () {
    return this._value
  }

  set value (newVal) {
    if (newVal !== this._value) {
      return newVal
    }
  }
}
export function ref(value) {
  return new RefImpl(value)
}

ref的实现大概就是这样, 导出一个ref方法,我们在使用ref(180000)的时候,内部其实是去new了一个RefImpl的。 在RefImpl里边我们去getset一个值value,这个是es6类里边的语法了。 监听这个value的变化, 然后还有一个_value私有的变量,作为一个oldValue去判断新老值,如果新老值不一致的时候,把newVal重新赋给_value.

  • 问: 为什么ref的响应式变量为什么使用的时候,我们需要.value了?
  • 答: 因为内部就是去监听这个value值的变化呀. 上边实现了RefImpl但是也只是监听到一下变量的变化,还没有干任何事情,我们需要的是,当我们get到了数据的时候,把我们需要做的事情,作为依赖绑定进去,当他变化了的时候,我们去执行我们需要做的事情。 这样的话,我们就需要有一个Dep依赖了。
export default class Dep {
  constructor () {
    this.dep = new Set()
  }
  notice () {
    this.dep.forEach((o) => o())
  }
  depend () {
    if (window.rootEffect) {
      this.dep.add(window.rootEffect)
    }
  } 
}

依赖的代码也很简单,主要做的就是,发布订阅,为什么用Set这个数据结构呢? 因为它可以给我们去重,保证数据的唯一性,我们就不用判断是否Set里边有没有这个值了。 depend方法里边,因为是简易的实现嘛,我这里为了省事,直接依赖的是一个全局变量window.rootEffect

const dep = new Dep()

class RefImpl {
  constructor (value) {
    this._value = value
  }

  get value () {
    dep.depend()
    return this._value
  }

  set value (newVal) {
   if (newVal !== this._value) {
      this._value = newVal
      dep.notice()
    }
    return this._value
  }
}

然后我们再在代码实现我们上边说的思路。 get的时候添加依赖,set的时候执行添加的所有依赖.

effect 实现

effect的实现就很简单了, 其实也算是比较巧妙的。

export const effect = function (fn) {
  window.rootEffect = fn
  fn()
  window.rootEffect = null
}

这里我们先把我们要做的事情给赋值到window.rootEffect变量里边,比如这个

effect(() => {
  wuYa = haoNan.value + 100
  console.log(wuYa)
})

由于变量里边有haoNan.value,那么则会触发RefImpl里边的get,然后get里边做了一件事情,dep.depend(), 则会把依赖收集进去了。 然后我们haoNan.value = 190000的时候,又触发了set,然后执行notice发布订阅。 把之前收集的函数执行一遍。 这样的话,就完成了我们之前的想法。 只要浩南报价,乌鸦就会自动跟着报价,并且价格只高浩南100块了。

结语

代码属于简易的实现,可能还有一些情况没有考虑到。 但是基本的实现原理大概就是这样了。 好了,那么明天见咯。 如果觉得文章有一些帮助的话,可以给我点一个赞哟。 (^o^)/~