Vue3.0源码学习——响应式原理(一)

875 阅读3分钟

「这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战」。

前言

本文对Vue3.0的响应式源码进行学习,Vue官方文档: 深入响应性原理

Vue 最独特的特性之一,是其非侵入性的响应性系统。数据模型是被代理的 JavaScript 对象。而当你修改它们时,视图会进行更新。

响应式API体验

Vue3 在 setup 中如果想使用响应式的数据,可以使用 reactiveRefs 这两个API

<body>
  <div id="app">
    <h1>vue3 reactivity API</h1>
    <p>{{ state.counter }}</p>
    <p>{{ counter2 }}</p>
  </div>
  <script>
    const app = Vue.createApp({
      setup() {
        const state = Vue.reactive({
          counter: 1
        })
        setInterval(() => {
          state.counter++
        }, 1000)
        Vue.watch(() => state.counter, () => {

        })

        const counter2 = Vue.ref(1)
        setInterval(() => {
          counter2.value++
        }, 1000)
        Vue.watch(counter2, () => {

        })

        return {
          state,
          counter2
        }
      }
    })

    app.mount('#app')
  </script>
</body>

使用上的区别

  • ref() 的作用就是将一个 原始数据类型(primitive data type) 转换成一个带有响应式特性, reactive() 是赋予 对象(Object) 响应式的特性

  • 使用或修改 ref 数据,需要带上 .value,使用或修改 rective 的数据直接使用即可

  • 导出以后在 html 中使用直接使用 ref需要 .value,使用 rective 中包裹的对象数据需要带上外层 key 值,比如 state.xxx

  • rective 中的数据可以通过toRef,toRefs 转换成 ref 导出在 html 直接使用

// 转换单个属性
const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, 'foo')

fooRef.value++
console.log(state.foo) // 2

state.foo++
console.log(fooRef.value) // 3

// 转换整个对象
const state = reactive({
  foo: 1,
  bar: 2
})
const stateAsRefs = toRefs(state)

// ref 和原始 property 已经“链接”起来了
state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3

reactive函数源码解析

  • 找到 reactive() 定义的位置 packages\reactivity\src\reactive.ts

  • reactive() 会返回一个 createReactiveObject() 执行后的结果,作用就是可以将普通的对象转为响应式对象

export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) {
    return target
  }
  // 如何将传入普通对象转换为响应式对象
  return createReactiveObject(
    target,
    false,
    mutableHandlers, /* 普通对象的代理处理器 */
    mutableCollectionHandlers, 
    reactiveMap
  )
}
  • createReactiveObject() 中对第一个参数 target(即reactive包裹的对象)做了一层 Proxy 代理 MDN Proxy 文档
  • 一般传入的都是普通对象,因此在 Proxy 中的 handler 会使用 baseHandlers 是在 reactive() 中传入的 mutableHandlers
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  ...
  
  // 创建代理对象,将传入的原始对象作为代理目标
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}
  • mutableHandlers 位置 packages\reactivity\src\baseHandlers.ts,其中对 gettersetter 做了拦截操作
export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}
  • getter 时分别对代理对象类型做了判断并且做了对应的操作,这里说一下如果是对象并且不是只读的情况下,会执行到 track() 去收集依赖
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    ...

    // target是数组的处理
    const targetIsArray = isArray(target)

    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    // target是对象
    const res = Reflect.get(target, key, receiver)

    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    // 如果不是只读
    if (!isReadonly) {
      // 建立target,key和依赖函数之间的关系
      track(target, TrackOpTypes.GET, key)
    }

    if (shallow) {
      return res
    }

    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    // 如果是对象递归处理
    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
  }
}
  • track 位置 packages\reactivity\src\effect.ts,会将收集到的依赖放到 depsMap 对象中,然后跟踪依赖 trackEffects ,在将来某个时候触发更新
// 依赖收集
export function track(target: object, type: TrackOpTypes, key: unknown) {
  ...
  
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = createDep()))
  }
  ...
  // 依赖跟踪
  trackEffects(dep, eventInfo)
}
  • 收集完了依赖,可以在单步调试中观察数据修改后,是怎样触发更新的,具体将在下一篇中进行讲解