Vue 3.0响应式系统编程概述

随着Vue3.0的正式发布,前端又多了一门需要学习的功课,本文主要是对vue3.0响应式系统的简单剖析及实现:

一、 3.0响应式

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

二、 核心方法

  • reactive (将对象转换为响应式对象)
  • ref (将基本类型的值转为具有一个value属性的响应式对象)
  • toRefs (解构响应式对象数据)
  • computed
  • effect(watch依赖的的底层函数)
  • track(收集依赖)
  • trigger (触发更新)

三、 proxy回顾

1. set和deleteProperty中需要返回布尔类型的值

'use strict'
// 问题1: set和deleteProperty中需要返回布尔类型的值
// 严格模式下,如果返回false的话,会出现TypeError的异常
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) 
    // 这里得写return 不写默认返回undefined
    //Reflect.set执行成功或者失败会返回布尔类型的值
  },
  deleteProperty(target, key) {
    // delete target[key]
    return Reflect.deleteProperty(target, key) // 这里得写return
  }
})

proxy.foo = 'zzz'

2. Proxy和Reflect中使用receiver

  • Proxy中receiver: Proxy或者继承Proxy的对象
  • Reflect中receiver:如果target对象设置了getter,getter中的this指向receiver

执行this.bar的时候,如果第三个参数receiver不传,getter中this指向obj对象,此时proxy.fooundefined
执行this.bar的时候,如果第三个参数receiver传, this指向代理对象,也就是获取target.bar, 此时proxy.foovalue - bar

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'
    }
    // 执行this.bar的时候,如果第三个参数receiver不传,getter中this指向obj对象,此时proxy.foo 为undefined
    // 执行this.bar的时候,如果第三个参数receiver传, this指向代理对象,也就是获取target.bar, 此时proxy.foo 为value - bar
    return Reflect.get(target, key, receiver)
  }
})
console.log(proxy.foo) // value - bar

四、 源码实现

4.1 reactive(只能转换对象)

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

代码实现:

// 工具方法
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
            console.log('get', key)
            const result = Reflect.get(target, key, receiver);
            return convert(result)
        },
        set (target, key, value, receiver) {
            let result = true;
            const oldValue = Reflect.get(target, key, receiver)
            if (oldValue !== value) {
                result = Reflect.set(target, key, value, receiver)
                //此处触发更新 - trigger
                console.log('set', key, value)
            }
            return result
        },
        deleteProperty(target, key) {
            const haskey = hasOwn(target, key)
            const result = Reflect.deleteProperty(target, key)
            if (haskey && result) {
                //此处触发更新 - trigger
                console.log('delete', key)
            }
            return result
        }
    }

    return new Proxy(target, handler);
}

实际使用:

<body>
  <script type="module">
    import { reactive } from './reactivity/index.js'
    const obj = reactive({
      name: 'zs',
      age: 18
    })
    obj.name = 'lisi'
    delete obj.age
    console.log(obj)
  </script>
</body>

输出结果:

set name lisi
delete age
Proxy {name: “lisi”}

4.2 依赖收集

weakMap:弱引用,当失去引用会被销毁

vue3.0中依赖是一个三层的树形结构,我们会在最外层定义一个new weakMap()的集合targetMap,当我们触发get时使用track方法收集依赖时首先判断当前是否存在一个activeEffect对象,不存在直接返回,存在则首先判断当前targettargetMap的集合中是否存在,如果不存在就在targetMap的集合中创建对应的集合depsMap,然后判断当前keydepsMap中是否存在,如果不存在就在depsMap的集合中创建对应的集合dep,将对应的依赖activeEffect收集到对应key值对应的dep集合中。

在这里插入图片描述

4.3 effect、 track

  • effect:参数为一个函数(第一次会执行一次),当函数的响应式对象发生改变,就会重新执行一次函数
  • track: 收集依赖的函数
let activeEffect = null; //当前活动的函数
export function effect(callback) {
    activeEffect = callback; //设置当前活动对象
    callback() //此时会访问响应式对象的属性,需要收集依赖
    activeEffect = null;
}
let targetMap = new WeakMap() // 收集依赖的集合

export function track(target, key) { //收集依赖的函数
    if (!activeEffect) return
    let depsMap = targetMap.get(target) //获取当前依赖集合中target对应的值
    if(!depsMap) { 
        targetMap.set(target, (depsMap = new Map())) //不存在则设置一个target对应的new Map()值
    }
    let dep = depsMap.get(key) //获取当前依赖集合中target对应的集合中 key属性对应的值
    if(!dep) {
        depsMap.set(key, (dep = new Set()))//不存在则在depsMap中设置一个key属性对应的new Set()值
    }
    dep.add(activeEffect) //将当前的活动对象添加到key属性对应的 Set集合中
}

4.4 trigger 触发更新

export function trigger(target, key) {//触发更新
    const depsMap = targetMap.get(target); // 找到target对象对应的集合
    if (!depsMap) return;
    const dep = depsMap.get(key) //找到key对应的dep集合
    if(dep) {//执行每个依赖于key(响应式对象的值)值函数
        dep.forEach(effect => effect())
    }
}

4.5 ref

可以接受对象或者基础类型,如果是响应式对象直接返回,是对象则内部会调用reactive将其转换为响应式对象,如果是普通的值则转为具有一个value属性的响应式对象

export function ref(raw) {
  // 判断raw是否是ref创建的对象,如果是的话直接返回
  if (isObject(raw) && raw.__v_isRef)return

  let value = convert(raw)
  const r = {
    __v_isRef: true, //特殊标识
    get value () {
      track(r, 'value') //收集依赖
      return value
    },
    set value (newValue) {
      if(newValue !== value) {
        raw = newValue
        value = convert(raw) //将得到的新值设置为响应式对象
        trigger(r, 'value') // 触发更新
      }
    }
  }
  return r
}

以上我们可以知道 reactive vs ref

  • ref可以把基本数据类型数据转换成响应式对象

  • ref返回的对象,重新赋值成对象也是响应式的

  • reactive返回的对象,重新赋值丢失响应式

  • reactive返回的对象不可解构

  • reactive

    const product = reactive({
      name: 'iPhone',
      price: 5000,
      count: 3
    })
    
  • ref

    const price = ref(5000)
    const count = ref(3)
    

4.6 toRefs

传入的对象必须是reactive返回的响应式对象(proxy对象)然后将传入对象的属性转换为类似ref返回的对象然后将属性挂载在一个新的对象上返回,如果不是响应式对象(proxy对象)直接返回.


export function toRefs (proxy) {
  //如果是数组我们创建一个新的数组 否则返回空对象
  const ret = proxy instanceof Array ? new Array(proxy.length) : {}

  for (const key in proxy) {
      // 将每一项转换为类似ref的对象
    ret[key] = toProxyRef(proxy, key)
  }

  return ret
}

function toProxyRef (proxy, key) {
  const r = {
    __v_isRef: true,
    get value () {
      return proxy[key]//这里不收集依赖是因为proxy是响应式对象,当我们访问响应式对象属性会触发get方法自动收集依赖
    },
    set value (newValue) {
      proxy[key] = newValue//这里不需要触发更新是因为proxy是响应式对象,当我们重新赋值会触发响应式对象的set方法触发更新
    }
  }
  return r
}

4.7 computed

接受一个有返回值的函数作为参数,返回的值就是计算属性的值并且会监听函数中的响应式数据的变化


  export function computed (getter) {
  const result = ref()

  effect(() => (result.value = getter()))

 	 	return result
  }

五、完整示例代码

https://gitee.com/liannian9/fed-e-task-03-05/tree/master/code/%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86/01-reactivity

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值