vue3.0响应式原理(二)

Vue3的响应式主要是通过proxy handlers来实现的,上一节有讲,Vue3的proxy handlers分为baseHandlers与collectionHandlers,这里主要讲baseHandlers。baseHandles又分为mutableHandlers与readonlyHandlers,顾名思义后者是只读的handlers

export const mutableHandlers: ProxyHandler = {
  get: createGetter(false),
  set,
  deleteProperty,
  has,
  ownKeys
}

export const readonlyHandlers: ProxyHandler = {
  get: createGetter(true),

  set(target: any, key: string | symbol, value: any, receiver: any): boolean {
    if (LOCKED) {
      if (__DEV__) {
        console.warn(
          `Set operation on key "${String(key)}" failed: target is readonly.`,
          target
        )
      }
      return true
    } else {
      return set(target, key, value, receiver)
    }
  },

  deleteProperty(target: any, key: string | symbol): boolean {
    if (LOCKED) {
      if (__DEV__) {
        console.warn(
          `Delete operation on key "${String(
            key
          )}" failed: target is readonly.`,
          target
        )
      }
      return true
    } else {
      return deleteProperty(target, key)
    }
  },

  has,
  ownKeys
}

两者的实现原理基本一致,这里主要讲mutableHandlers,实现响应式关键在于set和get方法,set主要用于发布(触发),get主要用于订阅(收集依赖)

先看订阅的实现

function createGetter(isReadonly: boolean) {
  return function get(target: any, key: string | symbol, receiver: any) {
    // console.log('get',target,key,receiver)
    const res = Reflect.get(target, key, receiver)
    if (isSymbol(key) && builtInSymbols.has(key)) {
      return res
    }
    if (isRef(res)) {
      return res.value
    }
    // console.log(isObject(res))
    track(target, OperationTypes.GET, key)
    return isObject(res)
      ? isReadonly
        ? // need to lazy access readonly and reactive here to avoid
          // circular dependency
          readonly(res)
        : reactive(res)
      : res
  }
}

这里分别为每段代码做个解释


if (isSymbol(key) && builtInSymbols.has(key)) {
  return res
}

这段代码判断key是否为内置的Symbol,如果是的话直接返回,不做依赖收集,例如:

const original = [1,2,3]
const observed = Vue.reactive(original)
Vue.effect(() => {
    const foo = observed.foo
    const testSymbol = observed[Symbol.iterator]
},{
    onTrack:({effect,target,type,key}) => {
        console.log(key)  // foo
    }
})

显然内置的Symbol并没有触发收集,至于effect副总用顾名思义,每当一个响应式的变量改变,会执行传入副作用内的匿名函数,

Vue3也是利用effect,每当值改变调用effect即组件生命周期update来重新执行页面,当然effect会在一开始执行一次以收集依赖,相对应的即组件生命周期的mounted。effect源码如下:

export function effect(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect {
  if (isEffect(fn)) {
    fn = fn.raw
  }
  const effect = createReactiveEffect(fn, options)
  if (!options.lazy) {
    effect()
  }
  return effect
}

此段代码主要2个功能,首先是判断传入的fn是否已经是响应式副作用函数的,如果是将fn置为fn.raw,例如:

const original = { foo: 1}
const observed = Vue.reactive(original)
const reactiveEffect = Vue.effect(() => {
      console.log(observed.foo)
})
const reactiveEffect2 = Vue.effect(reactiveEffect)
// 打印2次1

接下来就是根据传入的fn与options创建响应式副作用,源码如下:

function createReactiveEffect(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect {
  const effect = function reactiveEffect(...args: any[]): any {
    return run(effect, fn, args)
  } as ReactiveEffect
  effect[effectSymbol] = true
  effect.active = true
  effect.raw = fn
  effect.scheduler = options.scheduler
  effect.onTrack = options.onTrack
  effect.onTrigger = options.onTrigger
  effect.onStop = options.onStop
  effect.computed = options.computed
  effect.deps = []
  return effect
}

主要作用是创建一个effect函数,并将其effect标识符置为true,active状态置为true,deps依赖置为空数组,将传入的fn赋值给raw属性并将传入的option参数赋值给effect,effect执行函数讲run方法包装了一层返回,run函数主要源码如下:

function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
  if (!effect.active) {
    return fn(...args)
  }
  if (activeReactiveEffectStack.indexOf(effect) === -1) {
    cleanup(effect)
    try {
      activeReactiveEffectStack.push(effect)
      return fn(...args)
    } finally {
      activeReactiveEffectStack.pop()
    }
  }
}

首先判定effect是否是激活状态,如果非激活状态不进行依赖收集,直接执行函数内容,响应式副作用失去了响应式的功能。例如:

const original = { foo: 1}
const observed = Vue.reactive(original)
const reactiveEffect = Vue.effect(() => {
      console.log(observed.foo) // 1,1
})
Vue.stop(reactiveEffect)
reactiveEffect()
observed.foo = 2

注:Vue.stop函数需要自己引用出来,默认是不对外提供的,stop函数对应的源码是

export function stop(effect: ReactiveEffect) {
  if (effect.active) {
    cleanup(effect)
    if (effect.onStop) {
      effect.onStop()
    }
    effect.active = false
  }
}

主要是3个功能,第一个是清空effect的相关dep,第二个是调用传入effect的onStop函数,第三是讲effect激活状态置为false,

cleanup的源码如下

function cleanup(effect: ReactiveEffect) {
  const { deps } = effect
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}

effect的deps数组中每个对象指针指向与全局targetMap对应targetMap的对应dep所指针指向是相同的,当effect被删除后,即之后trigger时,不会再调用被删除的副作用。


接着讲run函数,activeReactiveEffectStack为当前运行effect的栈,因为effect内可能嵌套另外一个effect,类似于函数调用,每当调用一个effect时,将effect入栈,然后运行effect函数,effect内响应式对象出发proxy的get,然后将其与effect绑定,之后每当响应式对象发生变化时,调用与其绑定的effect函数。执行完effect之后即依赖收集完成,activeReactiveEffectStack pop出栈。

注意到还有2行代码,第一个是:

if (activeReactiveEffectStack.indexOf(effect) === -1) {
    // xx
}

这行代码即判断如果当前activeReactiveEffectStack中已经存在相同effect时候,不运行effect函数,主要作用是防止用户effect内嵌套相同effect,例如:

var observed = Vue.reactive({foo:10})
let effect = Vue.effect(() => {
    console.log(observed.foo)  // 显示一次10
    effect()
},{
    lazy:true
})
effect()

另外一行代码为:

cleanup(effect)

如果不删除之前的effect,那么则不会再次进行依赖收集,有可能会导致执行不必要的effect执行,不信将这行代码删除,运行如下代码:

var observed = Vue.reactive({foo:10})
var observed2 = Vue.reactive({num:1})
Vue.effect(() => {
    if(observed2.num === 1) {
        console.log('触发effect')  //触发了4次
    }else {
        let data = observed.foo
    }
})
observed2.num = 2
observed2.num = 1
observed.foo = 50
observed.foo = 60

很明显看出,当observed2.num为1时,observed.foo的值改变不需要重新执行effect。

你可能感兴趣的:(vue,vue3原理,vue3)