大前端学习-Vue3.0 响应式原理

1、Vue.js响应式回顾

  • Proxy对象实现属性监听
  • 多层属性嵌套,在访问属性过程中处理下一级属性
  • 默认监听动态添加的属性
  • 默认监听属性的删除操作
  • 默认监听数组索引和length属性
  • 可以作为单独的模块使用

核心方法

  • reactive / ref / toRefs / computed
  • effect
  • track
  • trigger

2、Proxy 对象回顾

'use strict'
// 问题1: set 和 deleteProperty 中需要返回布尔类型的值
//        在严格模式下,如果返回 false 的话会出现 Type Error 的异常
const target = {
  foo: 'xxx',
  bar: 'yyy'
}
// Reflect.getPrototypeOf()
// Object.getPrototypeOf()
const proxy = new Proxy(target, {
  get (target, key, receiver) {
    // return target[key]
    return Reflect.get(target, key, receiver)
  },
  set (target, key, value, receiver) {
    // target[key] = value
    return Reflect.set(target, key, value, receiver)
  },
  deleteProperty (target, key) {
    // delete target[key]
    return Reflect.deleteProperty(target, key)
  }
})

// proxy.foo = 'zzz'
// // delete proxy.foo

Proxy 和 Reflect 中使用的 receiver

  • Proxy 中 receiver:Proxy 或者继承 Proxy 的对象
  • Reflect 中 receiver:如果 target 对象中设置了 getter,getter 中的 this 指向 receiver
const obj = {
  get foo() {
    console.log(this)
    return this.bar
  }
}

const proxy = new Proxy(obj, {
  get (target, key, receiver) {
    if (key === 'bar') { 
      return 'value - bar' 
    }
    return Reflect.get(target, key, receiver)
  }
})
console.log(proxy.foo)

3、响应式原理 reactive

  • reactive 接受一个参数,判断这个参数是否是响应式对象
  • 创建拦截器对象 handler, 设置 get / set / deleteProperty
  • 返回Proxy 对象

1、在页面上加载ES Module 的方式来实现,在 index.js 中, 先创建 reactive 的形式,直接导出一个 reactive 函数,它接收一个参数 target,
2、在函数中首先要判断是否是对象,如果不是的话直接返回,否则将target 对象转化为代理对象,定义辅助函数isObject用来判断变量是否是对象。

3、定义一个hander 对象 里面有 get / set / deleteProperty 方法, 首先在 get 方法中要去收集依赖,然后返回target 对应key 的值,通过 Reflect.get 来获取

4、注意: 如果当前key 属性对应的值还是对象,那么还需要将它转化为响应式对象, 如果有嵌套对象,会在get 中递归
5、在 set 中,首先要去获取get 函数的值,判断值是否相等,不相等则调用Reflect更新值,在 deleteProperty 判断是否有自己的key 属性

const isObject = val => val !== null && typeof val === 'object';

const convert = target => isObject(target) ? reactive(target) : target;
const hasOwnProperty = Object.prototype.hasOwnProperty;
const hasOwn = (target, key) => hasOwnProperty.call(target, key)

export function reactive(target) {
  if (!isObject(target)) return target;

  const handler =  {
    get (target, key, receiver) {
      // 收集依赖
      console.log('get', key)
      const result = Reflect(target, key, receiver);
      return convert(result)
    },
    set (target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver);
      let result = true
      if (oldValue !== value) {
        Reflect.set(target, key, value, receiver);
        console.log('set', key, value)
        // 触发更新
      }
      return result
    },
    deleteProperty (target, key) {
      const hadKey = hasOwn(target, key);
      const result = Reflect.deleteProperty(target, key)

      if (hadKey && result) {
        // 触发更新
        console.log('delete', key)
      }
      return result
    }
  }
  return new Proxy(target, handler)
} 

在这里插入图片描述

4、响应式系统原理 - 收集依赖

通过 reactive 创建了一个响应式对象, effect 接收一个函数,和 watchEffect 用发一样,effect 内部首先会执行一次,当内部响应式数据变化,会再次执行

在收集依赖过程中会创建三个集合,targetMapdepMapsdeptargetMap的作用是用来记录目标对象和一个字典,也就是depMapstargetMapkey也就是目标对象

在这里插入图片描述

5、响应式原理 effect / track

effect 接收一个函数作为参数, 在effect 中首先要执行一次effect,在callback 中会访问响应式对象的属性,在这个过程中去收集依赖在收集依赖中,需要将callback 存储起来,定义一个activeEffect来存储callback,收集完毕,需要将 activeEffect `值设为初始值,因为如果有嵌套属性,是一个递归

let activeEffect = null;
export function effect (callback) {
  activeEffect = callback
  callback() // 访问响应式对象的属性,在这个过程中去收集依赖
}

// tarck 函数接收两个参数 target, key, 将target 存储到targetMap中
let targetMap = new WeakMap()

export function track (target, key) {
  if (!activeEffect) return
  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(activeEffect)
}

在代理对象中调用这个函数, reactive get

track(target, key)

6、响应式原理 触发更新 trigger

在 reactive set 中触发更新 调用trigger, deleteProperty 调用

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

完整代码

const isObject = val => val !== null && typeof val === 'object';

const convert = target => isObject(target) ? reactive(target) : target;
const hasOwnProperty = Object.prototype.hasOwnProperty;
const hasOwn = (target, key) => hasOwnProperty.call(target, key)

export function reactive(target) {
  if (!isObject(target)) return target;

  const handler =  {
    get (target, key, receiver) {
      // 收集依赖
      track(target, key)
      const result = Reflect(target, key, receiver);
      return convert(result)
    },
    set (target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver);
      let result = true
      if (oldValue !== value) {
        Reflect.set(target, key, value, receiver);
        // 触发更新
        trigger(target, key)
      }
      return result
    },
    deleteProperty (target, key) {
      const hadKey = hasOwn(target, key);
      const result = Reflect.deleteProperty(target, key)

      if (hadKey && result) {
        // 触发更新
        trigger(target, key)
      }
      return result
    }
  }
  return new Proxy(target, handler)
} 

// effect
let activeEffect = null;
export function effect (callback) {
  activeEffect = callback
  callback() // 访问响应式对象的属性,在这个过程中去收集依赖
}
let targetMap = new WeakMap()

export function track (target, key) {
  if (!activeEffect) return
  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(activeEffect)
}

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

测试

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script type="module">
    import { reactive, effect } from './reactivity/index.js'

    const product = reactive({
      name: 'iPhone',
      price: 5000,
      count: 3
    })
    let total = 0 
    effect(() => {
      total = product.price * product.count
    })
    console.log(total)

    product.price = 4000
    console.log(total)

    product.count = 1
    console.log(total)

  </script>
</body>
</html>

7、响应式原理- Ref

接收一个参数,可以是原始值,也可以是对象,如果传入的是对象,
直接返回,如果是普通对象,内部会调用reactive 创建响应式对象,
否则创建只有一个value 属性对象返回

测试

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script type="module">
    import { reactive, effect, ref } from './reactivity/index.js'

    const price = ref(5000)
    const count = ref(3)
   
    let total = 0 
    effect(() => {
      total = price.value * count.value
    })
    console.log(total)

    price.value = 4000
    console.log(total)

    count.value = 1
    console.log(total)

  </script>
</body>
</html>

reactive 和 ref

  • ref 可以把基本数据类型数据,转成响应式对象
  • ref 返回的对象,重新赋值成对象也是响应式的
  • reactive 返回的对象,重新赋值丢失响应式
  • reactive 返回的对象不可以解构

如果一个对象成员非常多的时候,使用ref 并不方便,因为总要带着value 属性,如果一个函数内部只有一个响应式数据,这个时候使用ref 会比较方便,因为可以解构返回
在这里插入图片描述

8、响应式系统原理- toRefs

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值