手写Vue3.0响应式

github源码地址

一. Reflect拦截器

Reflect(反射) 是一个内置的对象,它提供拦截 JavaScript 操作的方法。Reflect的所有属性和方法都是静态的(就像Math对象)。Reflect 对象提供了的静态方法与proxy handler methods的命名相同.其中的一些方法与 Object相同, 尽管二者之间存在 某些细微上的差别 .

二. proxy代理对象

语法

new Proxy(target, handler)

参数

  • target

    Proxy 会对 target 对象进行包装。它可以是任何类型的对象,包括内置的数组,函数甚至是另一个代理对象。

  • handler

    它是一个对象它的属性提供了某些操作发生时所对应的处理函数

proxy中使用Reflect的两个需要注意的问题

问题1:

set 和 deleteProperty 中需要返回布尔类型的值,在严格模式下,如果返回 false 的话会出现 Type Error 的异常.

const target = {
  foo: 'xxx',
  bar: 'yyy'
}
const proxy = new Proxy(target, {
  get (target, key, receiver) {
    return Reflect.get(target, key, receiver)
  },
  set (target, key, value, receiver) {
    // 如果返回 false 的话会出现 Type Error 的异常
    return Reflect.set(target, key, value, receiver)
  },
  deleteProperty (target, key) {
    // 如果返回 false 的话会出现 Type Error 的异常
    return Reflect.deleteProperty(target, key)
  }
})
proxy.foo = 'zzz'
delete proxy.foo

问题2:

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

const obj = {
  get foo() {
    console.log(this) // getter中的this指向receiver(也就是Proxy对象)
    return this.bar
  }
}

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

三. 为数据设置响应式-reactive

使用

<script type="module">
  import {reactive} from './reactivity/index.js';
  const obj = reactive({
    name:'zhangsan',
    age:'25',
    instrasting:{
      play:{
        bor : { name : 'footbor'}
      }
    }
  });
  const obj2 = reactive([1,2,3]);
  obj.name = 'zhangsan';
  delete obj.age;
  obj.sex = '男'
  console.log(obj.name);
</script>

原理实现

  • reactive接受一个目标参数,判断这个参数是否是对象
  • 是对象,创建拦截器对象handler,设置get/set/deleteProperty
    • 在get中判断取出的值是否是对象,是则递归调用reactive
  • 返回Proxy对象
const isObject = (target)=> target !== null && typeof target === 'object';
const convert = (target) => isObject(target) ? reactive(target) : target;
const isOwnProperty = (target,key) => Reflect.getOwnPropertyDescriptor(target,key);

export function reactive (target) {
    if(!isObject(target)) return target
    const handler = {
        get(target,key,receiver){
            console.log('get',key);
            // 递归 返回一个target 或 一个proxy对象
            return convert(Reflect.get(target,key,receiver))
        },
        set(target,key,value,receiver){
            const oldValue = Reflect.get(target,key,receiver);
            // set需要返回true,返回false的时候回报错
            if(oldValue !== value) {
                console.log('set',key,value);
                return Reflect.set(target,key,value,receiver)
            }
            return true
        },
        deleteProperty(target,key){
            // deleteProperty需要返回true,返回false的时候回报错
            // 判断target是否有key属性
            const hasKey = isOwnProperty(target,key);
            const result = Reflect.deleteProperty(target,key);
            if(hasKey && result){
                console.log('delete',key);
            }
          	return result
        }
    }
    return new Proxy(target,handler);
}

四. 依赖收集和触发-effect && track && trigger

使用

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

实现原理

  • effect函数首先会调用传入的执行函数.
  • 在执行函数中如果访问了响应式对象的属性,此时在get方法中会调用track函数去收集依赖.
    • 收集依赖的过程就是存储响应式对象对应的属性和属性对应的箭头函数.
      • 在track当中首先可以使用new WeakMap()以目标对象为key存储所有相关联的属性,而这些属性值又对应了不同的执行函数,即可以用new Map()以属性为key存储对应的执行函数,而每个响应式属性可能对应多个执行函数,固可将箭头函数存入到new set()中.
  • 在为响应式属性赋值的时候,会触发set方法中的trigger函数去触发更新.
    • 触发更新其实就是找到依赖收集过程中存储对象的属性及其对应的执行函数.找到函数之后立即执行.
const isObject = (target)=> target !== null && typeof target === 'object';
const convert = (target) => isObject(target) ? reactive(target) : target;
const isOwnProperty = (target,key) => Reflect.getOwnPropertyDescriptor(target,key);

export function reactive (target) {
    if(!isObject(target)) return target
    const handler = {
        get(target,key,receiver){
            // console.log('get',key);
            // 递归 依赖收集 返回一个target 或 一个proxy对象
            const result = convert(Reflect.get(target,key,receiver))
            if (result && activeEffect) {
                track(target,key)
            }
            return result
        },
        set(target,key,value,receiver){
            const oldValue = Reflect.get(target,key,receiver);
            // set需要返回true,返回false的时候回报错
            let result = true;
            if(oldValue !== value) {
                // console.log('set',key,value);
                result = Reflect.set(target,key,value,receiver);
                // 触发依赖
                trigger(target,key);
            }
            return result
        },
        deleteProperty(target,key){
            // deleteProperty需要返回true,返回false的时候回报错
            // 判断target是否有key属性
            const hasKey = isOwnProperty(target,key);
            let result = Reflect.deleteProperty(target,key);
            if(hasKey && result){
                // console.log('delete',key);
            }
          	return result
        }
    }
    return new Proxy(target,handler);
}

let activeEffect = null
export function effect (callback) {
    // 把callback存储起来
    activeEffect = callback;
    callback();//自执行一遍 触发对应get方法去依赖收集 依赖收集的时候需要用到activeEffect
    activeEffect = null; //当依赖收集完之后要清空存储 因为在依赖收集时如果有嵌套属性的话是一个递归的过程
}

// targetMap放在外面方便依赖收集与依赖触发
let targetMap = new WeakMap();
// track:依赖收集 把target存储到一个targetMap
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(target,key) {
    let depsMap = targetMap.get(target);
    if(!depsMap) return
    let dep = depsMap.get(key);
    if(!dep) return
     dep.forEach(effect => {
         effect()
     });
}

五. 基本数据类型的响应式-ref

ref vs reactive

  • ref可以把基本数据类型数据,转换成响应式对象
  • ref设置的响应式数据获取时需要使用value属性,模板中使用的时候可以省略value
  • ref返回的对象,重新给value属性赋值成对象之后也是响应式的,因为调用了convert设置响应式
  • reactive不能把基本类型数据转换成响应式对象
  • reactive返回的对象,重新赋值会丢失响应式.因为在set中没有递归convert设置响应式
  • reactive返回的Proxy对象解构出来的数据不是响应式,如果需要解构响应式数据的话需要使用toRefs来返回这个对象

使用

  <script type="module">
    import { reactive, effect, ref } from './reactivity/index.js'

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

    price.value = 4000
    console.log(total)

    count.value = 2
    console.log(total)
  </script>

实现原理

  • 判断传入的参数是否是ref创建的对象, 如果是的话直接返回
  • 如果传入object或array,那么直接调用reactive转成响应式
  • 否则返回一个具有__v_isRef:true,get value(),set value()属性的对象
// ref
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
}

六.引用数据类型解构-toRefs

toRefs

toRefs的作用是把reactive返回的对象的每一个属性转换成类似ref返回的对象,达到能对reactive返回对象解构的目的

使用

  <script type="module">
    import { reactive, effect, toRefs } from './reactivity/index.js'
    const {price,count} = toRefs(
      reactive({
        name: 'iPhone',
        price: 5000,
        count: 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>

实现原理

  • 判断proxy代理对象的是数组还是对象,生成一个空数组或空对象ret
  • 用for in遍历传入的proxy代理对象,存储toProxyKeys函数返回的ref对象
  • 最后返回ret可解构的响应式数据集
export function toRefs(proxy) {
  	// 判断proxy代理的是数组还是对象
    const ret = proxy instanceof Array ? new Array(proxy.length) : {};
    for (const key in toProxyKeys) {
      	// 转换成类似ref返回的对象
        ret[key] = toKeys(proxy,key)
    }
    return ret
}

function toProxyKeys(proxy,key) {
    const r = {
        __v_isRef:true, 
        get value(){
          	// 直接去代理对象中拿
            return proxy[key]
        },
        set value(newValue){
          	// proxy中的set会去判断新旧值
            proxy[key] = newValue
        }
    }
    return r
}

七. 计算属性computed

computed的作用是接收一个有返回值的函数作为参数,并监听这个函数内部响应式数据的变化,最后把这个函数执行的结果返回.这个返回值就是计算属性的值.它是响应式的.

使用

  <script type="module">
    import { reactive, computed  } from './reactivity/index.js'
    const product = reactive({
        name: 'iPhone',
        price: 5000,
        count: 3
    })
    const total = computed(() => {
      return product.price * product.count
    })
    console.log(total.value)

    product.price = 4000
    console.log(total.value)

    product.count = 1
    console.log(total.value)
  </script>

实现原理

  • 接收一个有返回值的函数作为参数
  • 使用ref函数创建一个响应式数据result
  • 使用effect监听这个函数内部响应式数据的变化,变化后计算的值赋给result
  • 最后返回这个响应式的值result
export function computed(getter) {
    const result = ref();
    effect(()=>{
        result.value = getter()
    })
    return result
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值