Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【bigo】Vue3 设计原理之响应式 #75

Open
fayeah opened this issue Oct 18, 2021 · 0 comments
Open

【bigo】Vue3 设计原理之响应式 #75

fayeah opened this issue Oct 18, 2021 · 0 comments

Comments

@fayeah
Copy link

fayeah commented Oct 18, 2021

基本认知

JavaScript 本身没有响应式的特性,而 Vue 响应式系统则通过依赖追踪的关系,当响应式状态改变时视图会自动更新。

Ref 和 Reactive

我们知道 Vue3 的响应式系统完全重写了,跟 Vue2 原理不同。重写之后有两个及其常用的响应式 API :RefReactive,我们一起从原理的角度来分析这两个 API 的实现。

Reactive: 返回一个响应式的对象状态。该响应式转换是“深度转换”——它会影响传递对象的所有嵌套 property。

const state = reactive({
  count: 0
})
console.log(state.count) // 0

Ref: 返回一个可变的响应式对象,该对象作为一个响应式的引用维护着它内部的值,该对象只包含一个名为 value 的 property。

const count = ref(0)
console.log(count.value) // 0

Proxy 和 Reflect

Vue3 响应式的核心便是 ES6 中新的方法 Proxy 和 Rflection,由于这两个方法对 IE 完全不兼容,所以在大家在做技术栈选型时可得慎重,同时基于 vue3 我们可以使用更多更新的方法 😉,塞翁失马,焉知非福啊。

Proxy:

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

第一个参数 target:是 Proxy 包装的目标对象;

第二个参数 handler: 通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理的行为。

可以看出 Proxy 的 handler 跟 Object.defineProperty handler 的形式很相似哈,别急,咱继续往下走。

Reflect

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handlers (en-US)的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。

Reflect 的方法与 proxy handlers 方法相同,预示着为什么 Proxy 经常跟 Reflect 在一起使用。

一个简单的例子:

const duck = {
  color: 'white',
}

Reflect.has(duck, 'color');
// true

Proxy 中使用 Reflect:

let product = { price: 5, quantity: 4 }
let proxiedProduct = new Proxy(product, {
  get(target, key, receiver) {
    console.log('receiver', receiver);
    return Reflect.get(target, key, receiver)
  },
  set(target, key, value, receiver) {
    return Reflect.set(target, key, value, receiver)
  }
})

get 方法中, target 即 Proxy 的第一个参数,代表 product 这个对象; key 则为要使用的属性,例如 price 这个属性;receiver,总是为当前的 Proxy 实例本身,即 proxiedProduct

回归响应式

最开始提到 vue 实现了响应式系统,当响应式状态改变时视图会自动更新,而涉及到响应式的概念包含:EffectTrack 以及 Trigger

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

    本人的浅解:当一个变量发生变化时,希望处理其他的操作,比如修改额外的数据等,是由副作用来实现的。依赖跟副作用的关系可以理解为,依赖是一个个副作用的集合(可以理解为:deps = [effect1, effect2, ...])。

  • Track:收集依赖。上面所述的副作用要进行的操作是我们所称为的依赖,而 Track 则是用于收集依赖,也就是说在某一个变量发生变化的时候,都需要的那些操作(都需要被触发的那些依赖)。

    track在 Proxy 的 get 方法中触发,由此能够自动保存需要追踪的依赖。

  • Trigger:触发依赖。在 Track 中保存了依赖之后,也应该能够让依赖自动执行。这部分工作时在 Trigger 中发生。

    由于 trigger 的操作需要在变量发生变化时自动执行,因此该 trigger 方法需要放在 Proxy 的 set中执行。

Reactive 的实现

基于以上的理解,综合起来的 reactive 方法可以得到一个简化的版本:

let targetMap = new WeakMap()
const track = (target, key) => {
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  dep.add(effect)
}

const trigger = (target, key) => {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => effect())
  }
}

let reactive = (target) => {
  const handler = {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver)
      track(target, key)
      return result
    },
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver)
      trigger(target, key)
      return result
    }
  }
  return new Proxy(target, handler)
}

Ref 的实现

我们知道 Ref 只有一个属性为 value,而它又是响应式的,基于以上的理论,大概有两种实现:

  1. 利用 reactive

    const ref = intialValue => reactive({value: intialValue});
    
  2. 使用对象的属性访问器(getter, setter)

    const ref = raw => {
     const r = {
       get value(){
         track(r, 'value');
       return raw;
     },
    
     set value(newVal){
         raw = newVal;
       trigger(r, 'value');
     }
    }
     return r;
    }
    
    

好啦,vue3 的响应式原理基本就到这里啦。这篇文章可能需要大家耐心地看,熟悉 vue2 以及写过 vue3 的盆友会比较读起来可能比较容易,很抱歉我没能写到大多数人都能不费劲儿地看懂,以后我再努努力,尽量把不容易理解的东西写的更通俗易懂一些,后面应该还会有相关的系列文章,期待一下哦 ~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant