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


前言

当前已完成的 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?: boolean
  scheduler?: () => void
}

let activeEffect: EffectFn | null = null
const targetMap = new WeakMap()

export function effect(fn: EffectFn, options: EffectOptions = {}) {
  const effectFn = () => {
    activeEffect = effectFn
    const result = fn()
    activeEffect = null
    return result
  }

  if (options.scheduler) effectFn.scheduler = options.scheduler
  if (!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: T
  let dirty = true

  const runner = effect(getter, {
    lazy: true,
    scheduler: () => {
      dirty = true
      trigger(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: T
  constructor(value: T) {
    this._value = value
  }

  get value() {
    track(this, 'value')
    return this._value
  }

  set value(newVal: T) {
    if (newVal !== this._value) {
      this._value = newVal
      trigger(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] = val
      trigger(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 = 0

    effect(() => {
      dummy = obj.count
    })

    expect(dummy).toBe(0)

    // 修改属性 → 触发 trigger → 重新执行 effect
    obj.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 dummy

    effect(() => {
      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,而是执行 scheduler
    state.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)

    // 没访问前不会调用 getter
    expect(getter).not.toHaveBeenCalled()

    // 第一次访问,计算并缓存
    expect(c.value).toBe(2)
    expect(getter).toHaveBeenCalledTimes(1)

    // 第二次访问,走缓存
    c.value
    expect(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 dummy
    effect(() => {
      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 dummy

    effect(() => {
      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 = 0
    let calls = 0

    effect(() => {
      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 dummy
    effect(() => {
      dummy = countRef.value
    })

    expect(dummy).toBe(1)

    obj.count++
    expect(dummy).toBe(2)

    countRef.value = 3
    expect(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、保持响应式联动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TE-茶叶蛋

踩坑不易,您的打赏,感谢万分

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值