从简单案例到源码实现再到自己实现,带你一步梭哈Vue3响应式原理!

903 阅读4分钟

本文从案例出发:源码实现(详细注释) + 自己手写,直接梭哈。

文章很干,可以分几天看(慢慢来,别急

Vue3响应式原理

一.ReactiveAPI原理分析

export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  // 如果这个对象已经被 readonly代理过了,则直接返回。 被readonly代理过就会添加proxy,取值时会走get方法
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  return createReactiveObject( // 创建响应式对象
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers
  )
}
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  if (!isObject(target)) { // reactive只接受对象
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it. 目标已经是被代理过了
  // exception: calling readonly() on a reactive object 如果被reactive 处理过的对象还可以继续被readonly处理
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy  对象 和 代理 进行缓存
  const proxyMap = isReadonly ? readonlyMap : reactiveMap
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only a whitelist of value types can be observed.
  const targetType = getTargetType(target) // 看对象是否是可扩展的
  if (targetType === TargetType.INVALID) { // 不可扩展直接返回
    return target
  }
  const proxy = new Proxy( // 核心 创建proxy, 对集合的处理和普通的对象略有不同
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy) // 缓存起来
  return proxy
}

注:createReactiveObject实现分为几个步骤:

  • 1.如果不是对象就直接返回。
  • 2.如果目标对象已经被代理过,被reactive代理过的对象,还可以被readonly进行包装
  • 3.查看缓存中。看对象是否已经被代理过,如果代理过则直接返回
  • 4.检测对象是否可扩展。
  • 5.针对对象创建代理,并缓存代理对象
  • 6.返回代理对象

调试方法:

暂且更改开发时打包的入口文件 scripts/dev.js

const target = args._.length ? fuzzyMatchTarget(args._)[0] : 'reactivity'

案例1:

const { reactive, readonly } = VueReactivity;
let obj = { name: 'vue' };
let proxy1 = readonly(obj);
debugger;
let proxy2 = reactive(proxy1);
console.log(proxy1 === proxy2);

readonly代理过的对象,不能在使用reactive代理

案例2:

let obj = { name: 'vue' };
debugger
let proxy1 = reactive(obj);
let proxy2 = reactive(proxy1);
let proxy3 = reactive(obj);

对象不会被重复代理,同时如果已经是代理对象也不会再次进行代理

案例3:

let obj = { name: 'vue' };
let proxy = reactive(obj);
debugger
console.log(obj === toRaw(proxy));
let obj2 = { name: 'vue' };
let proxy2 = reactive(markRaw(obj2));
console.log(proxy2);

通过代理对象可以获取被代理数据,必要时可以使用markRaw标记对象,可以避免对象的代理操作

二.baseHandlers实现

export const mutableHandlers: ProxyHandler<object> = {
  get, // 访问属性代理
  set, // 设置对象属性代理
  deleteProperty, // 删除属性代理
  has, // 访问in操作触发
  ownKeys // 调用Object.getOwnPropertyNames()方法时触发
}

1).get访问器

//  不同类型的get  根据shallow  readonly 进行判断生成
const get = /*#__PURE__*/ createGetter()
const shallowGet = /*#__PURE__*/ createGetter(false, true)
const readonlyGet = /*#__PURE__*/ createGetter(true)
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    if (key === ReactiveFlags.IS_REACTIVE) { // 用来判断这个对象是reactive还是readonly
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) { // 如果取的是IS_READONLY 
      return isReadonly
    } else if (
      key === ReactiveFlags.RAW && // 可以使用toRaw方法获取被代理过对象对应的原值
      receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
    ) {
      return target
    }

    const targetIsArray = isArray(target)
    // 如果是数组 对 'includes', 'indexOf', 'lastIndexOf' 方法进行处理
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    const res = Reflect.get(target, key, receiver)

    if ( // 是内置symbol或者是原型链 查找到的,直接返回
      isSymbol(key)
        ? builtInSymbols.has(key as symbol)
        : isNonTrackableKeys(key)
    ) {
      return res
    }

    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key) // 依赖收集
    }

    if (shallow) {
      return res
    }

    if (isRef(res)) {  // 数组访问索引时 无需.value
      // ref unwrapping - does not apply for Array + integer key.
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res // 直接返回value
    }

    if (isObject(res)) { // 如果是对象 递归处理
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

案例1:

let proxyArr = reactive([1, 2, 3]);
// 访问数组的方法时会访问数组的长度
proxyArr.push(5); // 调用数组方法时 有暂停收集的功能和增加收集项的功能
proxyArr[Symbol.hasInstance]; // 访问内置属性不会依赖收集

let r = reactive({
    name: ref('vue')
});
console.log(r.name); // reactive 会判断里面是否包含ref,自动拆包
let r1 = reactive([ref(1), 2, 3, 4]); // 这种情况下不会拆包

2).set访问器

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    const oldValue = (target as any)[key]
    if (!shallow) {
      value = toRaw(value) // 如果设置的值是reactive过的 会被转化为普通对象
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value // 老的是ref 新的不是ref 则会给老的ref赋值
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }

    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    // 不触发原型链上的trigger
    if (target === toRaw(receiver)) { 
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value) // 新增
      } else if (hasChanged(value, oldValue)) { // 修改
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

案例1:

let proxy1 = reactive({ name: 'vue', age: ref(11) });
proxy1.name = reactive({ str: 'v' });
proxy1.age = ref(12);
console.log(proxy1);

如果设置的值是reactive或者ref的情况

案例2:

let obj = {};
let proto = {a:1}
let proxyProto = new Proxy(proto, {
    get(target,key,receiver) {
        return Reflect.get(target,key,receiver)
    },
    set(target,key,value,receiver){
        console.log(proxyProto , receiver == myProxy)
        return Reflect.set(target,key,value,receiver)
    }
})
Object.setPrototypeOf(obj,proxyProto); //原型链
let myProxy = new Proxy(obj,{
    get(target,key,receiver) {
        return Reflect.get(target,key,receiver)
    },
    set(target,key,value,receiver){
        console.log(receiver === myProxy)
        return Reflect.set(target,key,value,receiver)
    }
})
myProxy.a = 100

原型链也是一个proxy,那么通过Reflect.set方法设置值,会触发原型的set方法

你也尝试分析下baseHandlers中其他的访问器

三.collectionHandlers实现

// 集合中的方法 map.get  map.set, 最终都会调用get方法
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: createInstrumentationGetter(false, false)
}

export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: createInstrumentationGetter(false, true)
}

export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: createInstrumentationGetter(true, false)
}
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
  const instrumentations = shallow // 返回不同的getter
    ? shallowInstrumentations
    : isReadonly
      ? readonlyInstrumentations
      : mutableInstrumentations

  return (
    target: CollectionTypes,
    key: string | symbol,
    receiver: CollectionTypes
  ) => {
    if (key === ReactiveFlags.IS_REACTIVE) { // 处理 isReactive  isReadonly  toRaw
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.RAW) {
      return target
    }

    return Reflect.get( // 对map 、 set的方法进行处理
      hasOwn(instrumentations, key) && key in target
        ? instrumentations
        : target,
      key,
      receiver
    )
  }
}
const mutableInstrumentations: Record<string, Function> = {
  get(this: MapTypes, key: unknown) { 
    return get(this, key)
  },
  get size() {
    return size((this as unknown) as IterableCollections)
  },
  has,
  add,
  set,
  delete: deleteEntry,
  clear,
  forEach: createForEach(false, false)
}

set、map默认的方法进行代理

1).get访问器

function get( // 取值操作
  target: MapTypes,
  key: unknown,
  isReadonly = false,
  isShallow = false
) {
  // #1772: readonly(reactive(Map)) should return readonly + reactive version
  // of the value
  target = (target as any)[ReactiveFlags.RAW] 
  const rawTarget = toRaw(target)
  const rawKey = toRaw(key)
  if (key !== rawKey) { // key可能是对象也是被proxy过的
    !isReadonly && track(rawTarget, TrackOpTypes.GET, key)
  } // 对rawKey进行依赖收集
  !isReadonly && track(rawTarget, TrackOpTypes.GET, rawKey)
  const { has } = getProto(rawTarget) // 获取has方法
  const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive
  if (has.call(rawTarget, key)) { // 是否包含key
    return wrap(target.get(key))   // 如果是取到的值是对象接着代理
  } else if (has.call(rawTarget, rawKey)) {
    return wrap(target.get(rawKey))
  }
}

2).add访问器

function add(this: SetTypes, value: unknown) {
  value = toRaw(value) // 添加的值转为普通值
  const target = toRaw(this)  
  const proto = getProto(target) // 获取目标原型
  const hadKey = proto.has.call(target, value) // 是否有此value
  target.add(value) // 添加值
  if (!hadKey) { // 没有则触发add
    trigger(target, TriggerOpTypes.ADD, value, value)
  }
  return this
}

针对Set 处理,如果新增的才需要触发更新,已经有的就无所谓了。

3).set访问器

function set(this: MapTypes, key: unknown, value: unknown) {
  value = toRaw(value)
  const target = toRaw(this)
  const { has, get } = getProto(target)

  let hadKey = has.call(target, key) // 是否有key 
  if (!hadKey) { 
    key = toRaw(key)
    hadKey = has.call(target, key)
  } else if (__DEV__) {
    checkIdentityKeys(target, has, key)
  }

  const oldValue = get.call(target, key)
  target.set(key, value) // 添加数据
  if (!hadKey) {
    trigger(target, TriggerOpTypes.ADD, key, value) // 触发更新 -》 添加逻辑
  } else if (hasChanged(value, oldValue)) {
    trigger(target, TriggerOpTypes.SET, key, value, oldValue) // 触发修改
  }
  return this
}

针对map的操作

四.effect函数剖析

1) effect函数实现

const effectStack: ReactiveEffect[] = [] // 创建effect栈 包装effect执行顺序正确
let activeEffect: ReactiveEffect | undefined // 当前正在执行的effect
export function effect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
  if (isEffect(fn)) { // 是否已经是effect,是effect获取对应的原函数 
    fn = fn.raw
  }
  const effect = createReactiveEffect(fn, options) // 创建响应式effect
  if (!options.lazy) { // 是不是立即执行
    effect()
  }
  return effect
}
let uid = 0
function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect<T> {
  const effect = function reactiveEffect(): unknown {
    if (!effect.active) { // 如果不是active的,默认为true
      return options.scheduler ? undefined : fn()
    }
    if (!effectStack.includes(effect)) {
      cleanup(effect) // 每次重新收集依赖
      try {
        enableTracking() // shouldTrack = true
        effectStack.push(effect)
        activeEffect = effect
        return fn()
      } finally {
        effectStack.pop()
        resetTracking()
        activeEffect = effectStack[effectStack.length - 1]
      }
    }
  } as ReactiveEffect
  effect.id = uid++ // effect 唯一标识
  effect.allowRecurse = !!options.allowRecurse // 运行effect重复执行
  effect._isEffect = true // effect是不是effect
  effect.active = true // 是否激活
  effect.raw = fn // 对应的原函数
  effect.deps = [] // effect 对应的属性
  effect.options = options
  return effect
}

案例1:

// 1.已经是effect函数,再被effect
let reactiveEffect = effect(()=>{
    console.log(1);
});
debugger;
let reactiveEffect2 = effect(reactiveEffect);
console.log(reactiveEffect === reactiveEffect2); // false

案例2:

const state = reactive({name:'vue',age:11})
effect(()=>{
    console.log('rerender')
    if(state.name === 'vue'){
        console.log(state.age);
    }
})
state.age = 100;
state.name = 'vv';
state.age = 200;

每次属性需要重新收集对应的effect

五.依赖收集

export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (!shouldTrack || activeEffect === undefined) { // 是否要收集依赖
    return
  }
  let depsMap = targetMap.get(target) 
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  if (!dep.has(activeEffect)) { // 维护依赖收集结构
    dep.add(activeEffect)
    activeEffect.deps.push(dep) // 将dep 推入到deps中
    if (__DEV__ && activeEffect.options.onTrack) {
      activeEffect.options.onTrack({
        effect: activeEffect,
        target,
        type,
        key
      })
    }
  }
}

六.触发更新

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }

  const effects = new Set<ReactiveEffect>()
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
    if (effectsToAdd) { // 用于去重
      effectsToAdd.forEach(effect => {
        if (effect !== activeEffect || effect.allowRecurse) {
          effects.add(effect)
        }
      })
    }
  }

  if (type === TriggerOpTypes.CLEAR) { // 是清空,则调用所有effect
    // collection being cleared
    // trigger all effects for target
    depsMap.forEach(add)
  } else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => { // 数组更新
      if (key === 'length' || key >= (newValue as number)) {
        add(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    if (key !== void 0) { // 针对key来进行操作
      add(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) { // 针对map的情况
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) { // 如果增加了某一项 更新数组
          // new index added to array -> length changes
          add(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE: // 删除处理
        if (!isArray(target)) {
          add(depsMap.get(ITERATE_KEY)) 
          if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET: // 针对map的修改处理
        if (isMap(target)) {
          add(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const run = (effect: ReactiveEffect) => {
    if (__DEV__ && effect.options.onTrigger) {
      effect.options.onTrigger({
        effect,
        target,
        key,
        type,
        newValue,
        oldValue,
        oldTarget
      })
    }
    if (effect.options.scheduler) { // 有scheduler选项,会调用scheduler
      effect.options.scheduler(effect)
    } else {
      effect() // 否则直接执行effect
    }
  }

  effects.forEach(run)
}

案例1:

const state = reactive({name:'vue',age:11});
effect(()=>{
    debugger
    state.name;
});
debugger;
state.name = 'zs'

调试track和effect函数

案例2:

const state = reactive({ name: 'vue', age: 11 });
effect(()=>{ state.name },{
    scheduler:(effect)=>{
   		 console.log('更新')
    }
})
state.name = 'vv'

effect支持传入scheduler参数

手写丐版响应式原理

一.响应式API实现

const { reactive, shallowReactive, readonly, shallowReadonly } = VueReactivity
let obj = { name: 'vue', age: { n: 11 } }
const state = reactive(obj)
const state = shallowReactive(obj)
const state = readonly(obj)
const state = shallowReadonly(obj)

针对不同的API创建不同的响应式对象

import {
    mutableHandlers,
    readonlyHandlers,
    shallowReactiveHandlers,
    shallowReadonlyHandlers
} from "./baseHandlers"; // 不同的拦截函数

export function reactive(target) {
    return createReactiveObject(target, false, mutableHandlers)
}

export function shallowReactive(target) {
    return createReactiveObject(target, false, shallowReactiveHandlers)
}

export function readonly(target) {
    return createReactiveObject(target, true, readonlyHandlers)
}

export function shallowReadonly(target) {
    return createReactiveObject(target, true, shallowReadonlyHandlers)
}
/**
 * 
 * @param target 拦截的目标
 * @param isReadonly 是不是仅读属性
 * @param baseHandlers 对应的拦截函数
 */
function createReactiveObject(target, isReadonly, baseHandlers) {}

二.shared模块实现

{
  "name": "@vue/shared",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "module": "dist/shared.esm-bundler.js",
  "buildOptions": {
    "formats": [
      "esm-bundler",
      "cjs"
    ]
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

配置tsconfig.json识别引入第三方模块

{
    "baseUrl": ".",
    "moduleResolution": "node",
    "paths": {
      "@vue/*": [
        "packages/*/src"
      ]
    }
}

使用yarn install 将shared模块注入到node_modules中

三.createReactiveObject实现

Vue3中采用proxy实现数据代理, 核心就是拦截get方法和set方法,当获取值时收集effect函数,当修改值时触发对应的effect重新执行

import { isObject } from '@vue/shared'
const reactiveMap = new WeakMap(); 
const readonlyMap = new WeakMap();
function createReactiveObject(target, isReadonly, baseHandlers) {
    // 1.如果不是对象直接返回
    if(!isObject(target)){ 
        return target
    }
    const proxyMap = isReadonly ? readonlyMap : reactiveMap; // 获取缓存对象
    const existingProxy = proxyMap.get(target);
    // 2.代理过直接返回即可
    if(existingProxy){ 
        return existingProxy;
    }
    // 3.代理的核心
    const proxy = new Proxy(target,baseHandlers); 
    proxyMap.set(target,proxy);
	// 4.返回代理对象
    return proxy; 
}

baseHandlers实现

import { isObject } from "@vue/shared";
import { reactive, readonly } from "./reactive";

const get = createGetter();
const shallowGet = createGetter(false, true);
const readonlyGet = createGetter(true);
const shallowReadonlyGet = createGetter(true, true)

const set = createSetter();
const shallowSet = createSetter(true);

/**
 * @param isReadonly 是不是仅读
 * @param shallow 是不是浅响应
 */
function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
        const res = Reflect.get(target, key, receiver);

        if (!isReadonly) { // 如果是仅读的无需收集依赖
            console.log('依赖收集')
        }

        if (shallow) { // 浅无需返回代理
            return res
        }

        if (isObject(res)) { // 取值时递归代理
            return isReadonly ? readonly(res) : reactive(res)
        }
        return res;
    }
}

function createSetter(shallow = false) {
    return function set(target, key, value, receiver) {
        const result = Reflect.set(target, key, value, receiver);
        return result;
    }
}

export const mutableHandlers = {
    get,
    set
};
export const readonlyHandlers = {
    get: readonlyGet,
    set(target, key) {
        console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`)
        return true;
    }
};
export const shallowReactiveHandlers = {
    get: shallowGet,
    set: shallowSet
};
export const shallowReadonlyHandlers = {
    get: shallowReadonlyGet,
    set(target, key) {
        console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`)
        return true;
    }
};

四.effect实现

实现响应式effect

export function effect(fn, options: any = {}) {
    // 创建响应式effect
    const effect = createReactiveEffect(fn, options);
    // 默认会让effect先执行一次
    if (!options.lazy) {
        effect();
    }
    return effect
}
let uid = 0;
function createReactiveEffect(fn, options) {
    // 返回响应式effect
    const effect = function reactiveEffect() {
		// todo...
    }
    effect.id = uid++;// 用于做标识的
    effect._isEffect = true; // 标识是响应式effect
    effect.raw = fn; // 记录原本的fn
    effect.deps = []; // 用于收集effect对应的相关属性
    effect.options = options;
    return effect;
}

利用栈型结构存储effect,保证依赖关系

const state =reactive({name:'vue',age:12,address:'回龙观'})
effect(()=>{ // effect1      
    console.log(state.name); // 收集effect1          
    effect(()=>{ // effect2 
        console.log(state.age); // 收集effect2
    });
    console.log(state.address); // 收集effect1
})
const effect = function reactiveEffect() {
    if (!effectStack.includes(effect)) {
        try {
            effectStack.push(effect);
            activeEffect = effect; // 记录当前的effect
            return fn(); // 执行用户传递的fn -> 取值操作
        } finally {
            effectStack.pop();
            activeEffect = effectStack[effectStack.length - 1];
        }
    }
}

五.track依赖收集

function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
      	// ...
        if (!isReadonly) { // effect函数执行时,进行取值操作,让属性记住对应的effect函数
            track(target, TrackOpTypes.GET, key);
        }
    }
}
const targetMap = new WeakMap();
export function track(target, type, key) {
    if(activeEffect === undefined){ // 如果不在effect中取值,则无需记录
        return
    }
    let depsMap = targetMap.get(target);
    // WeakMap({name:'vue',age:11},{name:{Set},age:{Set}})
    if(!depsMap){ // 构建存储结构
        targetMap.set(target,(depsMap = new Map))
    }
    let dep = depsMap.get(key);
    if(!dep){
        depsMap.set(key,(dep = new Set));
    }
    if(!dep.has(activeEffect)){
        dep.add(activeEffect);
        activeEffect.deps.push(dep);
    }
}

六.trigger触发更新

对新增属性和修改属性做分类

function createSetter(shallow = false) {
    return function set(target, key, value, receiver) {
        const oldValue = target[key];
        const hadKey =
            isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key);
        const result = Reflect.set(target, key, value, receiver);
        if (!hadKey) {
            // 新增属性
            trigger(target, TriggerOpTypes.ADD, key, value)
        } else if (hasChanged(value, oldValue)) {
            // 修改属性
            trigger(target, TriggerOpTypes.SET, key, value, oldValue)
        }
        return result;
    }
}

将需要触发的effect找到依次执行

export function trigger(target, type, key?, newValue?, oldValue?) {
    const depsMap = targetMap.get(target);
    if (!depsMap) { // 属性没有对应的effect
        return
    }
    const effects = new Set(); // 设置集合
    const add = (effectsToAdd) => {
        if (effectsToAdd) {
            effectsToAdd.forEach(effect => {
                effects.add(effect);
            })
        }
    }
    if (key === 'length' && isArray(target)) { // 如果修改的是长度
        depsMap.forEach((dep, key) => {
            // 如果有长度的依赖要更新  如果依赖的key小于设置的长度也要更新 
            if (key == 'length' || key >= newValue) {
                add(dep)
            }
        });
    } else {
        if (key !== void 0) {
            // 修改key
            add(depsMap.get(key));
        }
        switch (type) {
            case TriggerOpTypes.ADD:
                if (isArray(target)) {
                    if (isIntegerKey(key)) { // 给数组新增属性,直接触发length即可
                        add(depsMap.get('length'))
                    }
                }
                break;
            default:
                break;
        }
    }
    effects.forEach((effect: any) => {
        effect();
    })
}

七.实现Ref

ref本质就是通过类的属性访问器来实现的,可以将一个普通值类型进行包装。

import { hasChanged, isObject } from "@vue/shared";
import { track, trigger } from "./effect";
import { TrackOpTypes, TriggerOpTypes } from "./operations";
import { reactive } from "./reactive";

export function ref(value) { // ref Api
    return createRef(value);
}

export function shallowRef(value) { // shallowRef Api
    return createRef(value, true);
}
function createRef(rawValue, shallow = false) {
    return new RefImpl(rawValue, shallow)
}

const convert = (val) => isObject(val) ? reactive(val) : val; // 递归响应式

class RefImpl {
    private _value;
    public readonly __v_isRef = true; // 标识是ref
    constructor(private _rawValue, public readonly _shallow) {
        this._value = _shallow ? _rawValue : convert(_rawValue)
    }
    get value() {
        track(this, TrackOpTypes.GET, 'value');
        return this._value;
    }
    set value(newVal) {
        if (hasChanged(newVal, this._rawValue)) {
            this._rawValue = newVal; // 保存值
            this._value = this._shallow ? newVal : convert(newVal);
            trigger(this, TriggerOpTypes.SET, 'value', newVal);
        }
    }
}

八.实现toRefs

class ObjectRefImpl{
    public readonly __v_isRef = true
    constructor(private readonly _object, private readonly _key) {}
    get value(){
        return this._object[this._key]
    }
    set value(newVal){
        this._object[this._key] = newVal
    }
}
export function toRef(object,key){
    return new ObjectRefImpl(object,key);
}
export function toRefs(object) {
    const ret = isArray(object) ? new Array(object.length) : {};
    for (const key in object) {
        ret[key] = toRef(object, key)
    }
    return ret;
}

将对象中的属性转换成ref属性

九.实现Computed

computed的整体思路和Vue2.0源码基本一致,也是基于缓存来实现的。

import { effect, track, trigger } from './effect';
import { isFunction } from '@vue/shared';
class ComputedRefImpl {
    private _value;
    private _dirty = true; // 默认是脏值
    public readonly effect;
    public readonly __v_isRef = true;
    constructor(getter, private readonly _setter) {
        this.effect = effect(getter, {
            lazy: true, // 计算属性特性
            scheduler: () => {
                if (!this._dirty) { // 依赖属性变化时
                    this._dirty = true; // 标记为脏值,触发视图更新
                    trigger(this, 'set', 'value');
                }
            }
        })
    }
    get value() {
        if (this._dirty) {
            // 取值时执行effect
            this._value = this.effect();
            this._dirty = false;
        }
        track(this,  TrackOpTypes.GET ,'value'); // 进行属性依赖收集
        return this._value
    }
    set value(newValue) {
        this._setter(newValue);
    }
}
export function computed(getterOrOptions) {
    let getter;
    let setter;
    if (isFunction(getterOrOptions)) { // computed两种写法
        getter = getterOrOptions;
        setter = () => {
            console.warn('computed value is readonly')
        }
    } else {
        getter = getterOrOptions.get;
        setter = getterOrOptions.set;
    }
    return new ComputedRefImpl(getter, setter)
}

effect.ts

 effects.forEach((effect: any) => {
     if(effect.options.scheduler){
         return effect.options.scheduler(effect); // 如果有自己提供的scheduler,则执行scheduler逻辑
     }
     effect();
 })

到了这里,你是否有了清晰的认知呢?

点个赞吧~