大前端学习笔记 -- Vue.js 3.0响应式系统原理

Vue.js 3.0响应式系统原理

文章内容输出来源:大前端高薪训练营

一、介绍

1. Vue.js响应式回顾

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

2. 核心函数

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

二、Proxy对象回顾

1. 在严格模式下,Proxy的函数得返回布尔类型的值,否则会报TypeError

Uncaught TypeError: ‘set’ on proxy: trap returned falsish for property ‘foo’

'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
  },
  deleteProperty(target, key) {
    // delete target[key]
    return Reflect.deleteProperty(target, key) // 这里得写return
  }
})

proxy.foo = 'zzz'

2. Proxy和Reflect中使用receiver

Proxy中receiver:Proxy或者继承Proxy的对象
React中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) // 执行this.bar的时候,this指向代理对象,也就是获取target.bar
  }
})
console.log(proxy.foo) // value - bar

如果return Reflect.get(target, key, receiver)写成return Reflect.get(target, key)的话,则响应式属性foo里面的this还是指向原本的对象obj,this.bar就是undefined,而传入了receiver之后,响应式属性里的this就指向新的响应式对象proxy,this.bar返回value - bar

三、reactive

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

自己实现reactive

function isObject(value) {
  return value !== null && typeof value === 'object'
}

function convert(target) {
  return isObject(target) ? reactive(target) : target
}

const hasOwnProperty = Object.prototype.hasOwnProperty

function hasOwn(target, key) {
  return hasOwnProperty.call(target, key)
}

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

  const handler = {
    get (target, key, receiver) {
      // 收集依赖
      // track(target, key) // 稍后解注释
      console.log('get', key)
      const ret = Reflect.get(target, key, receiver)
      return convert(ret)
    },
    set (target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver)
      let ret = true
      if (oldValue !== value) {
        ret = Reflect.set(target, key, value, receiver)
        // 触发更新
        // trigger(target, key) // 稍后解注释
				console.log('set', key, value)
      }
      return ret
    },
    deleteProperty (target, key) {
      const hasKey = hasOwn(target, key)
      const ret = Reflect.deleteProperty(target, key)
      if (hasKey && ret) {
        // 触发更新
        // trigger(target, key) // 稍后解注释
				console.log('detele', key)
      }
      return ret
    }
  }

  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
index.js:39 detele age
index.html:17 Proxy {name: “lisi”}

四、收集依赖

在这里插入图片描述

五、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)
  if(!depsMap) {
    targetMap.set(target, depsMap = new Map())
  }
  let dep = depsMap.get(key)
  if(!dep) {
    depsMap.set(key, dep = new Set())
  }
  dep.add(activeEffect)
}

六、trigger

export function trigger(target, key) { // 触发依赖
  const depsMap = targetMap.get(target)
  if(!depsMap)return
  const dept = depsMap.get(key)
  if(dept) {
    dept.forEach(effect => {
      effect()
    })
  }
}

使用:

<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) // 15000

    product.price = 4000
    console.log(total) // 12000

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

七、ref

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

实现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
}

使用:

<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) // 15000

    price.value = 4000
    console.log(total) // 12000

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

八、toRefs

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
}

使用

<body>
  <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) // 直接返回解构的product不是响应式对象,所以调用toRefs将reactive对象的每个属性都转化成ref对象
    }

    const { price, count } = useProduct()

    let total = 0
    effect(() => {
      total = price.value * count.value
    })
    console.log(total) // 15000

    price.value = 4000
    console.log(total) // 12000

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

九、computed

export function computed(getter) {
  const result = ref()
  effect(() => (result.value = getter()))
  return result
}

使用

<body>
  <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) // 15000

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

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

备注:trigger/track/effct是底层的函数,一般不用。使用computed代替effect的使用

©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页