Big front-end study notes-Vue.js 3.0 responsive system principle

Vue.js 3.0 responsive system principle

Article content output source: big front-end high-paying training camp

1. Introduction

1. Vue.js responsive review

  • Proxy object implements property monitoring
  • Multi-level attribute nesting, processing the next level of attributes in the process of accessing attributes
  • Dynamically added properties are monitored by default
  • The delete operation of the default monitoring attribute
  • Default monitoring array index and length property
  • Can be used as a separate module

2. Core functions

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

Two, Proxy object review

1. In strict mode, the Proxy function must return a Boolean value, otherwise it will report a 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. Use receiver in Proxy and Reflect

Receiver in Proxy: Proxy or an object
that inherits Proxy Receiver in React: If the target object has a getter, this in the getter points to the 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

If return Reflect.get(target, key, receiver)written return Reflect.get(target, key), the this in the responsive attribute foo still points to the original object obj, this.bar is undefined, and after the receiver is passed in, the this in the responsive attribute points to the new responsive object proxy, this.bar Return value - bar.

三、reactive

  • Accept a parameter, determine whether this parameter is an object
  • Create interceptor object handler, set get/set/deleteProperty
  • Return the Proxy object

Realize reactive by yourself

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

use:

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

The output is:

set name lisi
index.js:39 detele age
index.html:17 Proxy {name: “lisi”}

Fourth, collect dependencies

Insert picture description here

Five, 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)
}

Six, 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()
    })
  }
}

use:

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

Seven, ref

eactive vs ref

  • ref can convert basic data types into responsive objects

  • The object returned by ref is also responsive to reassignment to an object

  • Object returned by reactive, reassignment loses reactive

  • The object returned by reactive cannot be deconstructed

  • reactive

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

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

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

use:

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

Eight, 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
}

use

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

Nine, computed

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

use

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

Note: Trigger/track/effct is the underlying function, generally not used. Use computed instead of effect

Guess you like

Origin blog.csdn.net/jal517486222/article/details/108689672