手写 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日

相关文章

  • 「学习笔记」AC 自动机

    「学习笔记」AC 自动机 点击查看目录 目录 「学习笔记」AC 自动机 算法 问题 思路 代码 例题 Keywords Search 玄武密码 单词 病毒 最短母串 文本生成器 背单词 密码 禁忌 前置:「学习笔记」字符串基础:Hash,KMP与Trie。 好像对例题的讲解越来越抽象了? 算法 问题 求 \(n\) 个单词在一个长度为 \(m\) 的文章里出…

    算法与数据结构 2023年5月5日
    00
  • Java 数据结构算法Collection接口迭代器示例详解

    Java 数据结构算法 Collection 接口迭代器示例详解 如果你正在学习 Java 编程,那么数据结构和算法一定是一个不可避免的话题。在 Java 中,Collection 框架提供了许多有用的接口和类来管理和操作集合。其中,迭代器 Iterator 是 Collection 中最重要的接口之一,它提供了对集合元素进行迭代的方法。本文将对 Java …

    数据结构 2023年5月17日
    00
  • Go 语言数据结构之双链表学习教程

    Go 语言数据结构之双链表学习教程 一、前言 双链表是常见的数据结构,Go语言作为一种静态类型的语言,自带指针类型支持,因此在实现双链表时相对比较容易。本文中,我们将介绍双链表的基础理论和实践应用,并结合代码实现来详细讲解。 二、实现双链表的基本操作 1. 创建双链表 创建双链表需要定义链表中存储的元素类型,以及定义一个结构体来表示双链表中的一个节点。 ty…

    数据结构 2023年5月17日
    00
  • Redis高效率原因及数据结构分析

    Redis高效率原因及数据结构分析 Redis高效率的原因 Redis是一款高性能、高可靠性的内存数据库,其高效率的原因主要体现在以下几个方面: 1. 内存存储 Redis数据完全存储在内存中,而不是像传统的关系型数据库一样存储在磁盘中。内存的读写速度要远远快于磁盘的读写速度,因此Redis在数据读写时的速度非常快,能够达到每秒钟数百万次的读写操作。 2. …

    数据结构 2023年5月17日
    00
  • 数据结构之位图(bitmap)详解

    数据结构之位图(bitmap)详解 什么是位图? 位图,又称为比特图、Bitmap,是一种非常常用的数据结构。它是一种特殊的数组,只能存储0或1,可以用来表示一些二元状态,如二进制码、字符集、颜色等信息。在数据挖掘、工程设计、网络安全等领域都有广泛的应用。 位图的原理 位图的原理是用数据的位来表示某个元素对应的值。如果对应位为1,则代表该元素存在,否则代表该…

    数据结构 2023年5月17日
    00
  • 【ACM算法竞赛日常训练】DAY10题解与分析【月月给华华出题】【华华给月月出题】| 筛法 | 欧拉函数 | 数论

    DAY10共2题: 月月给华华出题 华华给月月出题 难度较大。 ? 作者:Eriktse? 简介:211计算机在读,现役ACM银牌选手?力争以通俗易懂的方式讲解算法!❤️欢迎关注我,一起交流C++/Python算法。(优质好文持续更新中……)?? 原文链接(阅读原文获得更好阅读体验):https://www.eriktse.com/algorithm/110…

    算法与数据结构 2023年4月17日
    00
  • C语言全面讲解顺序表使用操作

    C语言全面讲解顺序表使用操作 什么是顺序表 顺序表(Sequential List)是一种常见的数据结构,它由一组连续的存储单元组成,并且支持随机访问。通常我们使用数组来实现顺序表。 顺序表的基本操作 初始化 在使用顺序表之前,需要先进行初始化。顺序表的初始化包括两个步骤:指定顺序表的大小,申请内存空间。具体代码如下: #define MAXSIZE 100…

    数据结构 2023年5月17日
    00
  • C++ 数据结构之布隆过滤器

    C++ 数据结构之布隆过滤器 简介 布隆过滤器是一种用于快速判断一个元素是否存在于一个集合中的数据结构。它的空间效率和查询效率都比较高,在某些场景下,它可以代替传统的哈希表。 原理 布隆过滤器的基本原理是:将一个元素映射为多个位数组中的位置。在插入元素时,将这些位置上的值设置为1;在查询元素时,如果这些位置上的值都为1,则认为元素存在于集合中;否则认为元素不…

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