8.5.3 Vue.js 3.0响应式系统原理

本文为拉勾网大前端高薪训练营第一期笔记


Vue.js响应式

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

Proxy注意事项

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

    // 问题2: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)
  </script>

Reactive

只能处理对象,和ref不同

模拟reactive、effect、ref、toRefs、computed

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.get(target, key, receiver)
      return convert(result)
    },
    set (target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver)
      let result = true
      if (oldValue !== value) {
        result = 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)
}

let activeEffect = null
export function effect (callback) {
  activeEffect = callback
  callback() // 访问响应式对象属性,去收集依赖
  activeEffect = null
}

//targetMap记录target对象是否有depsMap
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()))
  }
	// depsMap记录某个key是否有dep这个Set,有就直接添加这个effect callback,没有就创建一个set然后添加effect callback
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  dep.add(activeEffect)
}

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

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
}

export function toRefs (proxy) {
  const ret = proxy instanceof Array ? new Array(proxy.length) : {}

  for (const key in proxy) {
    ret[key] = toProxyRef(proxy, key)
  }

  return ret
}

function toProxyRef (proxy, key) {
  const r = {
    __v_isRef: true,
    get value () {
      return proxy[key]
    },
    set value (newValue) {
      proxy[key] = newValue
    }
  }
  return r
}

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

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

  return result
}

使用effect举例

<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>
  1. 把obj转换成响应式
  2. effect触发一次,里面的product.price和product.count触发tract
  3. 这时去给targetMap里添加key:product, value:空Map作为depsMap
  4. 在这个depsMap里添加{key: price, value:空Set作为dep, key:count, value:空Set}
  5. 在dep里添加effect的callback
  6. 之后改动product.price和product.count都触发trigger
  7. 触发dep里的effect callback,让total变化

reactive vs ref

  • ref可以把基本数据类型数据,转成响应式对象
  • ref返回的对象,重新赋值成对象也是响应式的
  • reactive返回的对象,重新赋值丢失响应式,重新赋值不是proxy代理对象
  • reactive返回的对象不可以解构,想解构需要用toRefs处理reactive返回的对象

使用toRefs举例

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

    function useProduct () {
      const product = reactive({
        name: 'iPhone',
        price: 5000,
        count: 3
      })
      
      return toRefs(product)
    }

    const { price, count } = useProduct()

    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>

注意这里解构后需要price.value

使用computed举例

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

    const product = reactive({
      name: 'iPhone',
      price: 5000,
      count: 3
    })
    let 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>

同样注意computed得到的值需要.value拿到

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值