实现mini-vue3

点击下面链接看视频

视频地址

初始化项目

yarn init -y

image-20211128092127830

vue3源码采用的是monorerpo的管理方式,我们这就简单点的方式

创建包

image-20211128092330626

集成typescript

注意如果没有安装typescript需要先安装typescript

npx tsc --init

集成jest

yarn add jest @types/jest --dev

注意安装之后还是不能识别spec.ts文件

image-20211128093141916

需要在ts.config文件中配置

image-20211128093312925

配置测试脚本命令

image-20211128110422606

配置ts.config

image-20211128110620586

解决jest不兼容esmodule的问题

image-20211128110818586

配置一下babel

https://jestjs.io/docs/getting-started

image-20211128111525970

image-20211128111132836

image-20211128111418616

安装插件jest

实现收集依赖

先创建effect.spec.js

describe('effect', () => {
  it("enter", () => {
    const af = reactive({
      age: 1
    })

    /**
     * 所谓的收集依赖就是将effect下面的回调函数fn get的时候先放在一个容器中
     * set的时候在执行所有被收集起来的fn
     */
    let newAge
    effect(() => {
      newAge =af.age + 1
    })
    expect(newAge).toBe(2)

    af.age++
    expect(newAge).toBe(3)
  })


})

配置ts.config使其兼容es6

image-20211128115348636

reactive.spec.ts

import { reactive } from '../reactive'

describe("reactive", () => {
  it("enter", () => {
    const obj = { a: 1 }
    const proxyObj = reactive(obj)

    expect(proxyObj).not.toBe(obj)
    expect(proxyObj.a).toBe(1)
  })
})

下面代码完成上面测试

reactive.ts

export function reactive(raw) {
  return new Proxy(raw, {
    get(target, key) {
      const res = Reflect.get(target, key)
      // 依赖收集
      return res
    },
    set(target, key, value) {
      const res = Reflect.set(target, key, value)
      return res
    }
  })
}

effect.ts

let activeEffect
class ReactiveEffect {
  fn: any
  constructor(fn) {
    this.fn = fn
  }

  run() {
    activeEffect = this
    this.fn()
  }
}

/**
 * 依赖收集 收集的就是ReactiveEffect的实例
 * 
 */

export function effect(fn) {
  // fn
  const _effect = new ReactiveEffect(fn)

  _effect.run()
}

// target->key->dep
//收集依赖
let targetMap = new Map()
export function track(target, key) {
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }


  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }

  dep.add(activeEffect)

}
//触发set 更新
export function trigger(target, key) {
  let depsMap = targetMap.get(target)
  let dep = depsMap.get(key)

  for (let effect of dep) {
    effect.run()
  }
}

reactive.ts 添加量函数

image-20211129101248580

完成和effect相关的功能

runner

一句话概括这个功能:effect函数会有一个返回一个可执行函数,并且这个函数返回值是fn return出来的值

测试用例

  it("runner", () => {
    let a = 1
 
    const runner = effect(() => {
      a++
      return 'a'
    })

    expect(a).toBe(2)
    const r = runner()
    expect(a).toBe(3)
    expect(r).toBe('a')
  })

在effect 函数中return

image-20211130103042127

在run方法中return

image-20211130103103088

scheduler

scheduler 就是 effect能传入的第二个参数,而且这个参数应该是个函数 一开始不会被调用

等响应式对象再次更新的时候 会发现 scheduler会被调用 而第一个参数的回调函数不会再被调用了

测试用例

  it("scheduler", () => {
    let dummy
    let run: any
    const scheduler = jest.fn(() => {
      run = runner
    })
    const obj = reactive({ foo: 1 })
    const runner = effect(
      () => {
        dummy = obj.foo
      },
      { scheduler }
    )
    // scheduler 就是 effect能传入的第二个参数,而且这个参数应该是个函数 一开始不会被调用
    expect(scheduler).not.toHaveBeenCalled()
    expect(dummy).toBe(1)
    // 等响应式对象再次更新的时候 会发现 scheduler会被调用  而第一个参数的回调函数不会再被调用了
    obj.foo++
    expect(scheduler).toHaveBeenCalledTimes(1)
    expect(dummy).toBe(1)

    // 调用runner
    run()
    expect(dummy).toBe(2)
  })

stop

调用stop方法的runner函数 会有一次让响应式更新失效的情况

  it("stop",()=>{
    let dummy;
    const obj = reactive({props:1})

    const runner = effect(()=>{
      dummy = obj.prop
    })

    obj.prop = 2
    expect(dummy).toBe(2)
    stop(runner)
    obj.prop = 3
      //可以看到没有立即更新为3 因为上面的runner调用了stop方法
    expect(dummy).toBe(2)
    
    runner()
    expect(dummy).toBe(3)
  })

思路:让当前key 对应的dep 里面的依赖 被删除

image-20211130110520802

image-20211130110512919

image-20211130110536552

image-20211130110542335

第三集:优化下stop,完成readonly相关功能

优化stop功能

image-20211205101134593

obj.prop++ 测试在以前的stop测试不会通过,原因就是obj.prop++ 的等价操作就是 obj.prop = obj.prop +1。 其中在获取obj.prop的时候会再一次触发get收集依赖 ,所以上面的stop删除就相当于白删除了

配置launch.json

{
  "version": "0.2.0",
  "configurations": [
      {
          // 调试名称
          "name": "Jest",
          "type": "node",
          // 启动类型 分为launch(启动) 和 attach(附加)两种
          "request": "launch", 
          // 设置运行时可执行文件路径,默认是node可以是其他的执行程序,如npm、yarn
          "runtimeExecutable": "yarn", 
          // 传递给程序的参数
          "args": [
              "jest",
          ], 
          // 指定程序启动调试的目录
          "cwd": "${workspaceRoot}", 
          "sourceMaps": false,
          // 如果设置为std,则进程stdout / stderr的输出将显示在调试控制台中,而不是侦听调试端口上的输出
          "outputCapture": "std",
      },
  ]
}

解决

image-20211205101502380

image-20211205101519009

readonly

创建文件readonly.spec.ts

import { readonly } from '../reactive'

describe("", () => {
  it("happy path", () => {
    /**
     * 只读属性
     */
    const original = { foo: 1, bar: { a: 2 } }
    const wrapped = readonly(original)
    expect(wrapped).not.toBe(original)
    expect(wrapped.foo).toBe(1)
  })

    //当改变属性 触发set时发出警告
  it('warn', () => {

    console.warn = jest.fn();

    const user = readonly({
      age: 10
    })

    user.age = 11
    expect(console.warn).toBeCalled()
  })
})

创建一个baseHandler.ts文件 ,对reactive.ts里面的代码进行重构,

import { track, trigger } from './effect'

/**
 * 为节约内存,所以全局环境下存储高阶函数的返回值
 */
const get = createGetter()
const set = createSetter()
const readonlyGet = createGetter(true)


function createGetter(isReadonly = false) {
  return function get(target, key) {
    const res = Reflect.get(target, key)
    // 依赖收集
    if (!isReadonly) {
      track(target, key)
    }

    return res
  }
}


function createSetter() {
  return function set(target, key, value) {
    const res = Reflect.set(target, key, value)
    trigger(target, key)
    return res
  }
}

export const mutableHandlers = {

  get: get,
  set: set

}

export const readonlyHandlers = {
  get: readonlyGet,
  set(target, key, value) {
    console.warn('只读');
    return true
  }

}

reactive.ts

import { track, trigger } from './effect'
import { mutableHandlers, readonlyHandlers } from './baseHandlers'





export function reactive(raw) {
  return createActiveObject(raw, mutableHandlers)
}

export function readonly(raw) {
  return createActiveObject(raw, readonlyHandlers)
}


function createActiveObject(raw, baseHandlers) {
  return new Proxy(raw, baseHandlers)
}
实现isReactive和isReadonly

测试用例

image-20211205101846574

image-20211205101819196

解决

image-20211205101937675

image-20211205101759099

深层reactive

  it("recursion reactives", () => {
    const original = {
      nested: {
        foo: 1
      },
      array: [{ bar: 2 }]
    }

    const observed = reactive(original)
    expect(isReactive(observed.nested)).toBe(true)
    expect(isReactive(observed.array)).toBe(true)
    expect(isReactive(observed.array[0])).toBe(true)
  })

image-20211220110344964

shallowReadonly

shallowReadonly.spec.ts

import { isReadonly, shallowReadonly } from '../reactive';


describe("shallowReadonly", () => {
  it("no recursion", () => {
    // reactive值作用于对象的表层
    const props = shallowReadonly({ n: { foo: 1 } })
    expect(isReadonly(props)).toBe(true)
    expect(isReadonly(props.n)).toBe(false)
  })



  it('warn', () => {

    console.warn = jest.fn();

    const user = shallowReadonly({
      age: 10
    })

    user.age = 11
    expect(console.warn).toBeCalled()
  })
})




reactive.ts

image-20211220082754938

baseHandlers.ts

image-20211221091629173

image-20211221091644876

image-20211220082824746

isProxy

image-20211220204054945

image-20211220204124397

reactive.ts

image-20211220204156697

ref

import { effect } from '../effect'
import { ref } from '../ref'

describe("ref", () => {

  it("ref init", () => {
    const a = ref(1)
    expect(a.value).toBe(1)
  })

  it("be reactive", () => {
    const a = ref(1)
    let dummy;
    let calls = 0
    effect(() => {
      calls++
      dummy = a.value
    })

    expect(calls).toBe(1)
    expect(dummy).toBe(1)

    a.value = 2

    expect(calls).toBe(2)
    expect(dummy).toBe(2)
    // same value should not trigger

    a.value = 2
    expect(calls).toBe(2)
    expect(dummy).toBe(2)


  })


  it("should be recursion", () => {
    const a = ref({
      count: 1,
    })

    let dummy
    effect(() => {
      dummy = a.value.count
    })
    expect(dummy).toBe(1)
    a.value.count = 2
    expect(dummy).toBe(2)

  })
})

ref.ts

import { isObjet } from '../utils'
import { isTracking, trackEffects, triggerEffects } from './effect'
import { reactive } from './reactive'


class RefImpl {
  private _value: any
  // ref和reactive的区别就是ref只有一个value,所以dep只需单独一个,不需要reactive的复杂对应关系
  public dep
  private _rawValue: any
  constructor(value) {
    // _rawValue的声明 是因为害怕this._value进行reactive后变成proxy
    // 从而不方便下面set的比较
    this._rawValue = value
    this._value = isObjet(value) ? reactive(value) : value

    this.dep = new Set()
  }

  get value() {
    if (isTracking()) {
      trackEffects(this.dep)
    }

    return this._value
  }

  set value(newValue) {
    if (!Object.is(newValue, this._rawValue)) {
      this._rawValue = newValue
      // 注意:是先修改value的值然后进行trigger
      this._value = isObjet(newValue) ? reactive(newValue) : newValue
      triggerEffects(this.dep)
    }



  }
}

export function ref(value) {
  return new RefImpl(value)
}

image-20211220204332024

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值