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

yizhihongxing

下面是手写 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日

相关文章

  • Java数据结构之实现跳表

    Java数据结构之实现跳表,是一篇对跳表数据结构的详细讲解。 背景 跳表是一种基于有序链表的高效查找算法,它的查找时间复杂度为O(logn),相比于普通链表的O(n),具有很大的优势。本文将介绍跳表的实现过程。 实现跳表 1. 跳表结构体 跳表的数据结构体实现包含以下四项: 头结点head:表示链表的起始位置。 节点Node:跳表中的节点,包含表层链表和下层…

    数据结构 2023年5月17日
    00
  • Go语言数据结构之希尔排序示例详解

    Go语言数据结构之希尔排序示例详解 希尔排序简介 希尔排序,也称为缩小增量排序,是插入排序的一种更高效的改进版本;希尔排序是非稳定排序算法。 希尔排序的基本思想是已距离进行“减半”的插入排序;先将整个待排序的记录序列分割成若干个子序列,分别进行直接插入排序,待各子序列中的记录基本有序时,再将子序列合并为整体有序序列。 希尔排序的过程 从上面的简介中我们可以得…

    数据结构 2023年5月17日
    00
  • CSP-何以包邮?

    题目描述 新学期伊始,适逢顿顿书城有购书满 x 元包邮的活动,小 P 同学欣然前往准备买些参考书。一番浏览后,小 P 初步筛选出 n 本书加入购物车中,其中第 i 本(1≤i≤n)的价格为 ai 元。考虑到预算有限,在最终付款前小 P 决定再从购物车中删去几本书(也可以不删),使得剩余图书的价格总和 m 在满足包邮条件(m≥x)的前提下最小。 试帮助小 P …

    算法与数据结构 2023年5月11日
    00
  • 集合框架及背后的数据结构

    集合框架及背后的数据结构 集合框架是Java编程语言中的一组接口和实现类,用于存储数据的集合。集合框架中提供了许多不同类型的集合,包括List、Set、Map等。背后的数据结构是实现集合框架的关键,不同的数据结构适用于不同的集合类型和场景。 集合框架中的接口和实现类 Java中的集合框架定义了一些接口以及这些接口的实现类,在使用Java集合的时候,主要是使用…

    数据结构 2023年5月17日
    00
  • Java实题演练二叉搜索树与双向链表分析

    Java实题演练二叉搜索树与双向链表分析 题目描述 给定一个二叉搜索树,将它转换成一个排序的双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。 思路分析 对于一颗二叉搜索树,有以下性质: 若左子树不为空,则左子树上所有结点的值均小于它的根结点的值; 若右子树不为空,则右子树上所有结点的值均大于它的根结点的值; 左右子树也为二叉搜索树。 我们考虑…

    数据结构 2023年5月17日
    00
  • PAT甲级真题1020.树的遍历

    翻译和代码思路:Acwing 一个二叉树,树中每个节点的权值互不相同。 现在给出它的后序遍历和中序遍历,请你输出它的层序遍历。 输入格式 第一行包含整数 N,表示二叉树的节点数。 第二行包含 N个整数,表示二叉树的后序遍历。 第三行包含 N 个整数,表示二叉树的中序遍历。 输出格式 输出一行 N个整数,表示二叉树的层序遍历。 数据范围 1<=N<…

    算法与数据结构 2023年4月17日
    00
  • C#数据结构与算法揭秘一

    C#数据结构与算法揭秘 准备工作 首先,需要在电脑上安装好Visual Studio开发环境。然后,从官网下载并安装书籍代码和演示程序。代码和示例程序都是基于.NET Framework 4.5.1平台,所以需要该版本或以上的.NET Framework。 第一章:初识数据结构和算法 该章节介绍了数据结构和算法的概念、学习数据结构和算法的重要性、以及该书的学…

    数据结构 2023年5月17日
    00
  • C语言实现数据结构迷宫实验

    C语言实现数据结构迷宫实验攻略 简介 迷宫是计算机图形学中的一个经典问题,也是数据结构和算法中常见的题目。C语言是一种广泛使用的编程语言,具有充分的编程接口和功能,可以方便地实现迷宫算法和数据结构。 本文将详细讲解C语言实现数据结构迷宫实验的完整攻略,让读者能够更加深入地理解迷宫算法和数据结构的应用。 实现步骤 1. 创建迷宫结构体 首先需要创建一个迷宫结构…

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