当前位置: 首页 > news >正文

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、保持响应式联动

相关文章:

  • Java ResourceBundle 资源绑定详解
  • linux find命令妙用
  • Kettle下载安装教程
  • Set系列之HashSet源码分析:原理剖析与实战对比
  • Ubuntu 24.04 终端美化
  • 强化学习之基于无模型的算法之基于值函数的深度强化学习算法
  • 望获实时Linux系统荣获人形机器人技术突破奖
  • 得物可观测平台架构升级:基于GreptimeDB的全新监控体系实践
  • 多通道经颅电刺激器的主流厂家介绍
  • 柯希霍夫积分法偏移成像中数据分布不均匀的处理方法
  • 【题解】Codeforces Round 1019 (Div. 2) B.Binary Typewriter ~ E.Keep the Sum
  • 【赵渝强老师】使用TiDB的审计日志
  • Learning vtkjs之ImageStreamline
  • URP - 公告牌的效果实现
  • 运维仙途 第2章 日志深渊识异常
  • 《多端统一的终极答案:X5内核增强版的渲染优化全解析》
  • AI赋能烟草工艺革命:虫情监测步入智能化时代
  • 栈与队列 Part 6
  • AI HR新范式:易路iBuilder如何通过“技术隐身,价值凸显”,成为HR身份转型的好帮手
  • 消防岗位技能竞赛流程方案策划
  • “五一”假期首日国铁郑州局迎大客流,预计发送旅客逾95万人次
  • 证监会:坚决拥护党中央对王建军进行纪律审查和监察调查决定
  • 民营经济促进法出台,自今年5月20日起施行
  • 解放日报:中国大模型企业的发展机遇已经到来
  • 日本希望再次租借大熊猫,外交部:双方就相关合作保持密切沟通
  • 言短意长|新能源领军者密集捐赠母校