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.pswp.cn/news/903665.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/903665.shtml
英文地址,請注明出處:http://en.pswp.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…