vue解构赋值_vue.js 3.0响应式系统原理实现

本文深入探讨Vue.js 3.0的响应式系统,讲解了Proxy如何实现属性监听,以及reactive、effect、track、trigger、ref、toRefs等核心概念。通过实例代码和测试用例,详细解析了Vue.js 3.0中解构赋值的响应式原理。
摘要由CSDN通过智能技术生成

1)Vue.js 响应式回顾

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

2)Proxy

'use strict'
    // 问题1: set 和 deleteProperty 中需要返回布尔类型的值
    //        在严格模式下,如果返回 false 的话会出现 Type Error 的异常
    const target = {
      foo: 'xxx',
      bar: 'yyy'
    }
    // Reflect.getPrototypeOf()
    // Object.getPrototypeOf()
    const proxy = new Proxy(target, {
      // 访问
      // receiver 表示当前的 proxy 对象或者继承的 proxy对象
      get (target, key, receiver) {
        // return target[key]
        // Reflect(用来操作对象的成员) 反射 es6中新增的成员 代码运行期间用来获取或者设置对象的成员
        // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
        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)

3)reactive

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

4)收集依赖

1545b4a2a6bdbc8d317674516e898a53.png

5)effect / track / trigger

  • effect 跟踪属性变化并调用回调函数
  • tarck 收集依赖
  • trigger 触发更新

6)ref

把一个基本数据类型数据,转成响应式对象,后续通过 .value 使用

7)reactive vs ref

  • ref 可以把基本数据类型数据,转成响应式对象
  • ref 返回的对象,重新赋值成对象也是响应式的
  • reactive 返回的对象,重新赋值丢失响应式
  • reactive 返回的对象不可以解构

9e0f37ae2472ae2d066d9bb6acc91cf8.png

8)toRefs

将reactive()创建出来的响应式对象,转换为普通对象,只不过这个对象上的每个属性节点,都是ref()类型的响应式数据

9)实现代码

// index.js
// 判断是否是对象
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) {
            // 收集依赖
            // console.log('get', target)
            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)
                // 触发更新
                // console.log('set', key, value)
                trigger(target, key)
            }
​
            return result
        },
​
        deleteProperty (target, key) {
            const hadKey = hasOwn(target, key)
            const result = Reflect.deleteProperty(target, key)
            if (hadKey && result) {
                //触发更新
                // console.log('delete', key)
                trigger(target, key)
            }
​
            return result
        }
    }
​
    return new Proxy(target, handler)
}
​
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)
}
​
// 触发更新
export function trigger (target, key) {
    // console.log("  ~ file: index.js ~ line 79 ~ trigger ~ target", target)
    // console.log("  ~ file: index.js ~ line 87 ~ trigger ~ targetMap", targetMap)
    const depsMap = targetMap.get(target)
​
    // console.log("  ~ file: index.js ~ line 80 ~ trigger ~ depsMap", depsMap)
    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
    }
​
    // raw 如果是对象,调用 reactive 转换成响应式对象
    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()))
    effect(() => result.value = getter())
​
​
    return result
}

10)测试代码

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script type="module">
        import { reactive } from './index.js'
​
        const obj = reactive({
            name: 'xcc',
            age: 18
        })
​
        obj.name = 'nopear'
        delete obj.age
        console.log(obj)
    </script>
</body>
</html>

effect-demo.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script type="module">
    // import { reactive, effect } from './node_modules/@vue/reactivity/dist/reactivity.esm-browser.js'
    import { reactive, effect } from './index.js'
​
    const product = reactive({
      name: 'iPhone',
      price: 5000,
      count: 3
    })
    let total = 0 
    effect(() => {
        // console.log(111)
      total = product.price * product.count
    })
    console.log(total)
​
    product.price = 4000
    console.log(total)
​
    product.count = 1
    console.log(total)
​
  </script>
</body>
</html>

ref.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script type="module">
    import { reactive, effect, ref } from './index.js'
​
    const price = ref(5000)
    const count = ref(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>
</body>
</html>

toRefs.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script type="module">
    import { reactive, effect, toRefs } from './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>
</body>
</html>

computed.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script type="module">
    import { reactive, effect, computed } from './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>
</body>
</html>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值