Vue 3.0响应式系统编程概述

   日期:2020-09-29     浏览:158    评论:0    
核心提示:Vue 3.0响应式系统简单实现响应式系统原理3.x响应式回顾核心方法proxy回顾源码实现reactive(只能转换对象)依赖收集effect、 tracktrigger 触发更新ref - 可以接受对象或者基础类型,如果是响应式对象直接返回,是对象则内部会调用reactive将其转换为响应式对象,如果是普通的值则转为具有一个value属性的响应式对象toRefs - 传入的对象必须是reactive返回的响应式对象(proxy对象)然后将传入对象的属性转换为类似ref返回的对象然后将属性挂载在一个新的

Vue 3.0响应式系统编程概述

    • 一、 3.0响应式
    • 二、 核心方法
    • 三、 proxy回顾
    • 四、 源码实现
      • 4.1 reactive(只能转换对象)
      • 4.2 依赖收集
      • 4.3 effect、 track
      • 4.4 trigger 触发更新
      • 4.5 ref
      • 4.6 toRefs
      • 4.7 computed
    • 五、完整示例代码

随着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

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服