Vue3源码学习3-结合vitetest来实现mini-vue

文章目录

  • 前言
    • ✅ 当前已实现模块汇总(mini-vue)
    • ✅ 每个模块简要源码摘要
      • 1. `reactive.ts`
      • 2. `effect.ts`
      • 3. `computed.ts`
      • 4. `ref.ts`
      • 5. `toRef.ts`
      • 6. `toRefs.ts`
    • ✅ 下一阶段推荐目标
    • 所有核心模块对应的 `__tests__` 测试文件,**带完整注释**
    • ✅ `reactive.spec.ts`
    • ✅ `effect.spec.ts`
    • ✅ `computed.spec.ts`
    • ✅ `ref.spec.ts`
    • ✅ `toRefs.spec.ts`
    • ✅ 总结:你已支持的模块对应测试


前言

当前已完成的 mini-vue 项目功能模块列表,以及每个模块对应 Vue 3 中的核心功能对照和简要源码说明:


✅ 当前已实现模块汇总(mini-vue)

模块文件名对应 Vue 3 功能描述
响应式核心reactive.tsVue.reactive通过 Proxy 实现深层响应式对象追踪
副作用收集effect.tsVue.effect依赖自动收集、调度器(scheduler)、懒执行(lazy)
计算属性computed.tsVue.computed支持懒计算、缓存、依赖追踪、调度触发
基本 Refref.tsVue.ref包装原始值为响应式对象,追踪 value 的读写操作
对象属性转 ReftoRef.tsVue.toRef将 reactive 对象的某个属性变为可独立响应式的 Ref
批量属性转 ReftoRefs.tsVue.toRefs将整个对象的所有属性都转为 Ref,支持结构保持响应式

✅ 每个模块简要源码摘要


1. reactive.ts

import { track, trigger } from './effect'export function reactive<T extends object>(target: T): T {return new Proxy(target, {get(target, key, receiver) {track(target, key)return Reflect.get(target, key, receiver)},set(target, key, value, receiver) {const result = Reflect.set(target, key, value, receiver)trigger(target, key)return result}})
}

2. effect.ts

type EffectFn = () => void
interface EffectOptions {lazy?: booleanscheduler?: () => void
}let activeEffect: EffectFn | null = null
const targetMap = new WeakMap()export function effect(fn: EffectFn, options: EffectOptions = {}) {const effectFn = () => {activeEffect = effectFnconst result = fn()activeEffect = nullreturn result}if (options.scheduler) effectFn.scheduler = options.schedulerif (!options.lazy) effectFn()return effectFn
}export function track(target: object, key: string | symbol) { ... }
export function trigger(target: object, key: string | symbol) { ... }

3. computed.ts

export function computed<T>(getter: () => T) {let value: Tlet dirty = trueconst runner = effect(getter, {lazy: true,scheduler: () => {dirty = truetrigger(obj, 'value')}})const obj = {get value() {track(obj, 'value')if (dirty) {value = runner()dirty = false}return value}}return obj
}

4. ref.ts

export class RefImpl<T> {private _value: Tconstructor(value: T) {this._value = value}get value() {track(this, 'value')return this._value}set value(newVal: T) {if (newVal !== this._value) {this._value = newValtrigger(this, 'value')}}
}export function ref<T>(value: T) {return new RefImpl(value)
}

5. toRef.ts

export function toRef<T extends object, K extends keyof T>(obj: T, key: K) {return {get value() {track(obj, key)return obj[key]},set value(val) {obj[key] = valtrigger(obj, key)}}
}

6. toRefs.ts

import { toRef } from './toRef'export function toRefs<T extends object>(obj: T) {const result: any = {}for (const key in obj) {result[key] = toRef(obj, key)}return result
}

✅ 下一阶段推荐目标

模块Vue 功能说明
isRefVue.isRef()判断对象是否为 ref 实例
unrefVue.unref()解包 ref 或返回原始值
watchVue.watch()监听响应式值变化,执行回调
readonlyVue.readonly()创建只读响应式对象,禁止写入
shallowRefVue.shallowRef()创建浅层响应式对象

所有核心模块对应的 __tests__ 测试文件,带完整注释

reactive.spec.ts

import { describe, it, expect } from 'vitest'
import { reactive } from '../src/reactive'
import { effect } from '../src/effect'describe('mini-vue: reactive', () => {it('should track and trigger on get/set', () => {const obj = reactive({ count: 0 })let dummy: number = 0effect(() => {dummy = obj.count})expect(dummy).toBe(0)// 修改属性 → 触发 trigger → 重新执行 effectobj.count++expect(dummy).toBe(1)})
})

effect.spec.ts

import { describe, it, expect, vi } from 'vitest'
import { reactive } from '../src/reactive'
import { effect } from '../src/effect'describe('mini-vue: effect', () => {it('should re-run effect on dependency change', () => {const state = reactive({ num: 1 })let dummyeffect(() => {dummy = state.num})expect(dummy).toBe(1)state.num++expect(dummy).toBe(2)})it('should allow scheduler', () => {const state = reactive({ foo: 1 })const scheduler = vi.fn()const runner = effect(() => state.foo, { scheduler })// 不会立即运行 effect,而是执行 schedulerstate.foo++expect(scheduler).toHaveBeenCalled()})
})

computed.spec.ts

import { describe, it, expect, vi } from 'vitest'
import { reactive } from '../src/reactive'
import { computed } from '../src/computed'
import { effect } from '../src/effect'describe('mini-vue: computed', () => {it('should compute lazily and cache', () => {const state = reactive({ count: 1 })const getter = vi.fn(() => state.count + 1)const c = computed(getter)// 没访问前不会调用 getterexpect(getter).not.toHaveBeenCalled()// 第一次访问,计算并缓存expect(c.value).toBe(2)expect(getter).toHaveBeenCalledTimes(1)// 第二次访问,走缓存c.valueexpect(getter).toHaveBeenCalledTimes(1)// 修改依赖,缓存失效state.count++expect(c.value).toBe(3)expect(getter).toHaveBeenCalledTimes(2)})it('should work inside effect', () => {const state = reactive({ count: 1 })const c = computed(() => state.count)let dummyeffect(() => {dummy = c.value})expect(dummy).toBe(1)state.count++expect(dummy).toBe(2)})
})

ref.spec.ts

import { describe, it, expect } from 'vitest'
import { ref } from '../src/ref'
import { effect } from '../src/effect'describe('mini-vue: ref', () => {it('should hold a value', () => {const r = ref(1)expect(r.value).toBe(1)})it('should be reactive', () => {const r = ref(1)let dummyeffect(() => {dummy = r.value})expect(dummy).toBe(1)r.value++expect(dummy).toBe(2)})it('should not trigger if value not changed', () => {const r = ref(1)let dummy = 0let calls = 0effect(() => {calls++dummy = r.value})expect(calls).toBe(1)r.value = 1 // 设置相同值,effect 不应重新执行expect(calls).toBe(1)})
})

toRefs.spec.ts

import { describe, it, expect } from 'vitest'
import { reactive } from '../src/reactive'
import { toRef } from '../src/toRef'
import { toRefs } from '../src/toRefs'
import { effect } from '../src/effect'describe('mini-vue: toRef / toRefs', () => {it('toRef should convert property to ref', () => {const obj = reactive({ count: 1 })const countRef = toRef(obj, 'count')let dummyeffect(() => {dummy = countRef.value})expect(dummy).toBe(1)obj.count++expect(dummy).toBe(2)countRef.value = 3expect(obj.count).toBe(3)})it('toRefs should convert all properties to refs', () => {const obj = reactive({ count: 1, name: 'vue' })const refs = toRefs(obj)expect(refs.count.value).toBe(1)expect(refs.name.value).toBe('vue')refs.count.value++expect(obj.count).toBe(2)obj.name = 'mini-vue'expect(refs.name.value).toBe('mini-vue')})
})

✅ 总结:你已支持的模块对应测试

模块名测试文件已验证功能
reactivereactive.spec.tsget/set 追踪触发
effecteffect.spec.ts基础响应式绑定、scheduler 调度
computedcomputed.spec.ts懒计算、缓存、响应式依赖更新
refref.spec.tsvalue 包装、响应式触发、去重
toRef/toRefstoRefs.spec.ts属性映射为 ref、保持响应式联动

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/903665.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PH热榜 | 2025-04-30

1. Daytona 标语&#xff1a;安全且灵活的基础设施&#xff0c;用于运行你的人工智能生成的代码。 介绍&#xff1a;Daytona Cloud 为 AI 智能体重塑了基础设施&#xff0c;具备不到 90 毫秒的启动时间、原生性能以及有状态执行的能力&#xff0c;这些是传统云计算所无法实现…

Android compileSdkVersion、minSdkVersion、targetSdkVersion的关系以及和Unity的关系

compileSdkVersion、minSdkVersion、targetSdkVersion的关系 参考&#xff1a;https://mp.weixin.qq.com/s?__bizMzg5MzYxNTI5Mg&mid2247494238&idx1&sn06285667d3ac1339f6d2daae840cedc8&chksmc125565280f1ad3aa127774c2d1e59eb2818f89f0cb3ed4d72145faf619…

数据库的死锁相关(一)

目录 前言 一、什么死锁 二、产生死锁的必要条件 三、死锁发生的具体位置和场景 1. 数据行级别死锁&#xff08;最常见&#xff09; 2. 表级别死锁 3. 索引间隙锁死锁&#xff08;InnoDB特有&#xff09; 4. 外键约束死锁 5. 元数据锁死锁 6. 内存中的锁结构死锁 7.…

Three.js + React 实战系列-3D 个人主页:构建 Hero 场景组件(项目核心)✨

在本节中&#xff0c;我们将完成整个 3D 主业项目中最核心的组件 —— Hero.jsx。 这个组件作为首页的主视觉部分&#xff0c;整合了 3D 模型、动画相机、交互按钮与自适应布局&#xff0c;构建出一个立体、酷炫、可交互的主场景。 前置准备&#xff1a; ✅安装依赖&#xff…

Electron Forge【实战】桌面应用 —— 将项目配置保存到本地

最终效果 定义默认配置 src/initData.ts export const DEFAULT_CONFIG: AppConfig {language: "zh",fontSize: 14,providerConfigs: {}, };src/types.ts export interface AppConfig {language: zh | enfontSize: numberproviderConfigs: Record<string, Recor…

RPG4.设置角色输入

这一篇是进行玩家移动和视角移动的介绍。 1.在玩家内进行移动覆写 virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override; 2.创建增强输入资产的变量创建 UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category "CharacterD…

[实战] Petalinux驱动开发以及代码框架解读

目录 Petalinux驱动开发以及代码框架解读一、引言二、步骤2.1 创建PetaLinux工程2.2 配置硬件描述文件2.3 设备树配置2.4 建立驱动框架2.5 编辑 .bb 文件2.6 编写驱动文件2.7 编写 Makefile2.8 验证配方配置2.9 集成驱动到 RootFS2.10 全系统编译与部署2.11 启动验证 三、框架解…

[特殊字符] 开发工作高内存占用场景下,Windows 内存压缩机制是否应该启用?实测分析与优化建议

在日常开发中&#xff0c;我们往往需要同时运行多个高占用内存的工具&#xff0c;例如&#xff1a; IntelliJ IDEA VMware 虚拟机 多个 Java 后端程序 这些应用程序非常“吃内存”&#xff0c;轻松就能把 16GB、甚至 24GB 的物理内存用满。那么&#xff0c;Windows 的“内存…

嵌入式学习笔记 - HAL_xxx_MspInit(xxx);函数

使用cubeMX生成的HAL库函数中&#xff0c;所有外设的初始化函数HAL_xxx_Init(&xxxHandle)中都存在有此调用函数HAL_xxx_MspInit(xxx)&#xff0c;此调用函数其实是对各外设模块比如UART&#xff0c;I2C等控制器使用的的底层硬件进行初始化&#xff0c;包括时钟&#xff0c;…

Nginx — http、server、location模块下配置相同策略优先级问题

一、配置优先级简述 在 Nginx 中&#xff0c;http、server、location 模块下配置相同策略时是存在优先级的&#xff0c;一般遵循 “范围越小&#xff0c;优先级越高” 的原则&#xff0c;下面为你详细介绍&#xff1a; 1. 配置继承关系 http 块&#xff1a;作为全局配置块&…

WPF之TextBlock控件详解

文章目录 1. TextBlock控件介绍2. TextBlock的基本用法2.1 基本语法2.2 在代码中创建TextBlock 3. TextBlock的常用属性3.1 文本内容相关属性3.2 字体相关属性3.3 外观相关属性3.4 布局相关属性 4. TextBlock文本格式化4.1 使用Run元素进行内联格式化4.2 其他内联元素 5. 处理长…

华为云loT物联网介绍与使用

&#x1f310; 华为云 IoT 物联网平台详解&#xff1a;构建万物互联的智能底座 随着万物互联时代的到来&#xff0c;物联网&#xff08;IoT&#xff09;已成为推动数字化转型的关键技术之一。华为云 IoT 平台&#xff08;IoT Device Access&#xff09;作为华为云的核心服务之…

AnimateCC教学:形状补间动画的代码实现

核心代码: var shape; var animationProps = {width: 50,height: 50,cornerRadius: 0,color: "#00FF00" }; function init() { shape = new createjs.Shape();shape.x = 200;shape.y = 150;stage.addChild(shape);// 初始绘制updateShape();// 设置补间动画createTw…

Android学习总结之Retrofit篇

1. 注解原理概述 在 Java 里&#xff0c;注解是一种元数据&#xff0c;它为代码提供额外信息但不影响程序的实际逻辑。注解可以在类、方法、字段等元素上使用&#xff0c;并且能在编译时、运行时通过反射机制被读取。Retrofit 充分利用了 Java 注解机制&#xff0c;通过自定义…

windows11 编译 protobuf-3.21.12 c++

下载 protobuf 包&#xff0c;本文使用 3.21.12 版本&#xff0c;Gitub下载链接&#xff1a; Github官网 , 网盘下载&#xff1a; 网盘 如果电脑环境没有安装 cmake 则需要安装&#xff0c;本文测试使用 cmake-3.25.1 版本&#xff0c; 下载地址&#xff1a;[camke-3.25.1] (…

Java继承中super的使用方法

super 关键字在 Java 中用于访问父类的成员&#xff08;包括字段、方法和构造函数&#xff09;。当你在子类中调用父类的方法或访问父类的成员变量时&#xff0c;super 是必不可少的工具。 &#x1f511; super 的基本用法 1. 调用父类的构造方法 在子类的构造方法中&#x…

网络安全之浅析Java反序列化题目

前言 这段时间做了几道Java反序列化题目&#xff0c;发现很多题目都是类似的&#xff0c;并且可以通过一些非预期gadget打进去&#xff0c;就打算总结一下常见的题目类型以及各种解法&#xff0c;并提炼出一般性的思维方法。 正文 分析入口点 拿到题目&#xff0c;有附件最…

动态规划问题,下降路径最小和(dp初始化问题,状态压缩),单词拆分(回溯法+剪枝+记忆化),substr函数

下降路径最小和 题目链接&#xff1a; 931. 下降路径最小和 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 给你一个 n x n 的 方形 整数数组 matrix &#xff0c;请你找出并返回通过 matrix 的下降路径 的 最小和 。 下降路径 可以从第一行中的任何元素开…

大数据治理自动化与智能化实践指南:架构、工具与实战方案(含代码)

📝个人主页🌹:一ge科研小菜鸡-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、引言:从人治到机治,数据治理正在进化 随着数据体量持续膨胀、数据场景复杂化,传统依赖人工规则的大数据治理方式已难以为继。企业在治理过程中面临: 数据质量问题激增,人工检测成本高 元数…

Golang - 实现文件管理服务器

先看效果&#xff1a; 代码如下&#xff1a; package mainimport ("fmt""html/template""log""net/http""os""path/filepath""strings" )// 配置根目录&#xff08;根据需求修改&#xff09; //var ba…