手写 Vue3 响应式系统(核心就一个数据结构)

下面是手写 Vue3 响应式系统的完整攻略。

1. 概述

Vue3 的响应式系统使用了 Proxy 对象来监测对象的变化,相较于 Vue2 的响应式系统使用 Object.defineProperty 进行数据劫持,Proxy 具有更好的性能和更简洁的 API。

当我们修改 Vue3 中的 reactive 对象内部的数据时,就会触发依赖收集和派发更新的操作,这样就能保证视图的自动更新。

Vue3 响应式系统的核心可以归纳为一个数据结构:Reactive Object,它包含了 target、handlers、depsMap 三个部分。

  • target:需要被监测的对象。
  • handlers:代理处理器,用于拦截对 Reactive Object 的操作。
  • depsMap:依赖收集器,用于存储 Reactive Object 内部所有属性的依赖关系。

2. 实现步骤

2.1 创建 Reactive Object

首先,我们需要编写 createReactiveObj 函数,用于创建一个响应式对象。

function createReactiveObj(target) {
  const handlers = {
    get(target, key, receiver) {
      // TODO: 依赖收集
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      // TODO: 派发更新
      return Reflect.set(target, key, value, receiver)
    }
  }

  const reactiveObj = new Proxy(target, handlers)

  return reactiveObj
}

这里我们使用 Proxy 对象创建了一个代理处理器 handlers,其中 get 方法用于实现依赖收集,set 方法用于实现派发更新。

2.2 实现依赖收集

在 get 方法中,我们需要实现依赖收集的操作,即将属性和对应的依赖存储到 depsMap 中。

//... 在createReactiveObj函数中省略部分代码

function createReactiveObj(target) {
  //... 省略部分代码
  const depsMap = new Map()

  const handlers = {
    get(target, key, receiver) {
      // 依赖收集:如果当前存在 activeEffect,将其加入依赖列表
      if (activeEffect) {
        let dep = depsMap.get(key)
        if (!dep) {
          dep = new Set()
          depsMap.set(key, dep)
        }
        dep.add(activeEffect)
      }

      return Reflect.get(target, key, receiver)
    },
    //... 省略部分代码
  }

  const reactiveObj = new Proxy(target, handlers)

  return reactiveObj
}

这里我们使用 Map 和 Set 来存储依赖关系,将 target 对象的属性作为键,将对应的依赖函数作为值。

2.3 实现派发更新

在 set 方法中,我们需要实现派发更新的操作,即通知 depsMap 中存储的所有依赖函数执行。

//... 在createReactiveObj函数中省略部分代码

let activeEffect = null

function createReactiveObj(target) {
  //... 省略部分代码
  const depsMap = new Map()

  const handlers = {
    //... 省略部分代码
    set(target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver)
      const result = Reflect.set(target, key, value, receiver)
      if (result && oldValue !== value) {
        // 派发更新:执行依赖函数
        const dep = depsMap.get(key)
        if (dep) {
          dep.forEach(effect => effect())
        }
      }
      return result
    }
  }

  const reactiveObj = new Proxy(target, handlers)

  return reactiveObj
}

这里我们使用 activeEffect 变量来存储当前的依赖函数,将其加入依赖列表,然后在 set 方法中执行存储的依赖函数即可实现派发更新。

2.4 创建依赖函数

最后,我们需要编写 effect 函数,用于创建一个依赖函数。

function effect(fn) {
  activeEffect = fn
  fn()
  activeEffect = null
}

这里我们使用 activeEffect 变量来存储当前的依赖函数,执行依赖函数时,将其置为 null,以避免不必要的影响。

3. 示例说明

下面,我们通过两个例子演示如何使用手写 Vue3 响应式系统。

3.1 基本使用

const state = { count: 0 }

const reactiveState = createReactiveObj(state)

effect(() => console.log(reactiveState.count))

reactiveState.count += 1 // 控制台输出:1
reactiveState.count += 1 // 控制台输出:2

这里我们创建了一个响应式对象 reactiveState,将其赋值为 state 对象的代理对象。

然后,我们使用 effect 函数创建一个依赖函数,每次执行时打印 reactiveState.count 的值。

接着,我们对 reactiveState.count 进行了两次修改,会发现控制台依次输出了 1 和 2,即每次修改都触发了依赖函数的执行。

3.2 嵌套使用

const state = { count: 0, nested: { name: 'vue3' } }

const reactiveState = createReactiveObj(state)

effect(() => {
  console.log(reactiveState.count, reactiveState.nested.name)
})

reactiveState.nested.name = 'reactive' // 控制台输出:0 'reactive'
reactiveState.count += 1 // 控制台输出:1 'reactive'

这里我们创建了一个嵌套对象 state,其中包含了一个 count 属性和一个嵌套的对象 nested,包含一个 name 属性。

然后,我们使用 createReactiveObj 函数创建一个响应式对象 reactiveState。

接着,我们使用 effect 函数创建一个依赖函数,每次执行时打印 reactiveState.count 和 reactiveState.nested.name 的值。

最后,我们对 reactiveState.nested.name 和 reactiveState.count 进行了修改,会发现控制台依次输出了 0 'reactive' 和 1 'reactive',即每次修改都触发了依赖函数的执行,包括嵌套对象的属性。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:手写 Vue3 响应式系统(核心就一个数据结构) - Python技术站

(0)
上一篇 2023年5月17日
下一篇 2023年5月17日

相关文章

  • ES6新特性五:Set与Map的数据结构实例分析

    ES6新特性五:Set与Map的数据结构实例分析 ES6引入了Set和Map两种新的数据结构,可以帮助我们更方便地操作一些复杂的数据结构。本文将会分别介绍Set和Map的基本用法,并且提供一些实例说明,帮助大家更好地理解。 Set数据结构 基本用法 Set对象是一种无序的、无重复元素、容器类的数据结构。其基本用法如下: const set = new Set…

    数据结构 2023年5月17日
    00
  • Java数据结构与算法之单链表深入理解

    Java数据结构与算法之单链表深入理解攻略 什么是单链表? 单链表(Singly Linked List)是指一个节点只指向下一个节点的链表。 单链表由多个节点组成,每个节点有两个属性:数据域和指针域。数据域保存节点的数据,指针域保存下一个节点的指针,因此每个节点包含两个域:data和next。 单链表的基本操作 单链表常用的基本操作包括: 在链表头部添加元…

    数据结构 2023年5月17日
    00
  • C语言中数据结构之链式基数排序

    C语言中数据结构之链式基数排序 概述 链式基数排序是基数排序的一种实现方式。基数排序是一种桶排序算法,它通过将需要排序的数据分成多个桶,并且按照一定的顺序将数据从桶中取出来,以达到排序的目的。链式基数排序则使用了链表结构来实现桶的功能。 实现步骤 链式基数排序的实现步骤如下: 申请链表节点数组,并初始化链表头结点数组。链表的数量等于指定的基数,例如10进制的…

    数据结构 2023年5月17日
    00
  • Java数据结构之链表相关知识总结

    Java数据结构之链表相关知识总结 链表是一种非常常用的数据结构,它有许多实际应用,比如链表可以用来实现栈、队列、散列表和图等数据结构。在Java语言中,链表的实现方式主要有单向链表、双向链表和循环链表。 单向链表 单向链表是一种链表结构,每个节点包含两个元素:节点值和一个指向下一个节点的引用。链表的头结点(第一个节点)不包含值,仅包含指向链表中第一个实际节…

    数据结构 2023年5月17日
    00
  • JavaScript数据结构之链表的实现

    JavaScript数据结构之链表的实现 什么是链表 链表是一种线性数据结构,其中的元素在内存中不连续地存储,每个元素通常由一个存储元素本身的节点和一个指向下一个元素的引用组成。相比较于数组,链表具有如下优缺点: 优点:动态地添加或删除元素时,无需移动其它元素。(数组则需要移动其它元素) 缺点:不能随机地访问某个元素,必须从头开始顺序遍历。(而数组可以通过索…

    数据结构 2023年5月17日
    00
  • 【JavaScript快速排序算法】不同版本原理分析

    说明 快速排序(QuickSort),又称分区交换排序(partition-exchange sort),简称快排。快排是一种通过基准划分区块,再不断交换左右项的排序方式,其采用了分治法,减少了交换的次数。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速…

    算法与数据结构 2023年4月18日
    00
  • 在matlab中创建类似字典的数据结构方式

    当需要使用类似字典的数据结构时,Matlab中可以使用结构体来实现。结构体是一种有序的数据集合,每个元素都可以包含不同类型的数据(如字符串、数值等),并通过指定一个名称来唯一地标识该元素。 创建一个空结构体 使用struct函数可以创建一个空的结构体,可以使用下面的代码: st = struct; 添加键值对 可以将键值对添加到结构体中,可以使用下面的代码向…

    数据结构 2023年5月17日
    00
  • C语言 数据结构堆排序顺序存储(升序)

    C语言 数据结构堆排序顺序存储(升序)攻略 1. 堆排序概述 堆排序是一种常见的排序算法,通过构建最大堆或最小堆来实现排序。本文介绍的是使用顺序存储方式实现的最大堆排序,也就是升序排序。 2. 最大堆的定义和实现 最大堆指的是堆结构中父节点的值大于子节点的值,根节点的值最大。对于一棵完全二叉树,若父节点的下标为i,则其左子节点的下标为2i+1,右子节点的下标…

    数据结构 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部