区别

  • 源码采用 monorepo 方式进行管理,将模块拆分到 packages 目录中
  • Vue3 采用 ts 开发,增强类型检查。Vue2 采用 flow
  • Vue3 的性能优化,支持 tree-shaking , 不使用就不会被打包
  • Vue2 后期引入 RFC, 使每个版本改动可控 rfcs

  • Vue3 劫持数据采用 proxy, Vue2 劫持数据采用 defineProperty, defineProperty 有性能问题和缺陷
  • Vue3 中对模块编译进行了优化,编译时生成了 Block Tree, 可以对子节点进行收集,可以减少比较,并且采用了 patchFlag 标记动态节点
  • Vue3 采用 Composition API 进行组织功能,解决反复横条,优化重复逻辑(Mixin 带来的数据来源不清晰、命名冲突等问题),相比 options API 类型推断更加方便
  • 增加了 FragmentTeleportSuspense 组件

目录结构

  • reactivity 响应式系统
  • compiler-core 与平台无关的编译器核心
  • compiler-dom 针对浏览器的编译模块
  • compiler-sfc 针对 vue 文件的编译模块
  • compiler-ssr 针对服务端的编译模块
  • runtime-core 与平台无关的运行时核心(可以创建针对特定平台的运行时 - 自定义渲染器)
  • runtime-dom 针对浏览器的运行时。包括 DOM API, 属性, 事件处理等
  • runtime-test 用于测试
  • server-renderer 用于服务端渲染
  • shared 多个包之间共享的内容
  • size-check 用来测试代码体积
  • template-explorer 用于调试编译器输出的开发工具
  • vue 完整版本,包括运行时和编译时

安装依赖

yarn add typescript rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-json execa

注意:安装依赖的时候会提示忽略工作空间:--ignore-workspace-root-check
依赖 解释
typescript 支持 ts 语法
rollup 打包工具
rollup-plugin-typescript2 连接 rollup 和 ts
@rollup/plugin-node-resolve 解析 node 中的第三方模块
@rollup/plugin-json 支持引入 json
execa 开启子进程执行命令

Reactivity 响应式模块

reactivity 模块是 Vue3 的响应式模块。包含以下文件:

baseHandlers.ts
collectionHandlers.ts
computed.ts
effect.ts
index.ts
operations.ts
reactive.ts
ref.ts

接下来,我们一一的分析以上文件中的核心逻辑~

  • reactivity.ts 文件

我们都知道 Vue2.x 中,响应式系统有以下缺点:

  1. 无法监听到数组长度的改变和 length 的改变
  2. 对象新增和删除属性无法监听
  3. 只能劫持对象的属性

该文件中最核心的方法就是:reactive 方法,该方法的主要作用是将目标对象转换为 Proxy 实例

export function reactive(target: object) {
  // 我们需要将目标变成响应式对象,Proxy实例
  return createReactiveObject(target, mutableHandlers) // 核心的操作就是读取文件时做依赖收集,当数据变化时做视图更新
}

createReactiveObject() 方法根据不同的参数返回不同的 proxy 实例, 有以下几种实例:

  1. 完全响应式的 proxy 实例,如果有嵌套对象,会 递归 调用 reactive
  2. 只读的 proxy 实例
  3. 浅层响应式的 proxy 实例(只有第一层的属性是响应式的)
  4. 只读的浅层响应式的 proxy 实例

扩展:浅层的响应式 proxy 实例是什么?

由于 proxy 只代理对象的第一层属性,更深层次的属性不会代理。如果需要完全响应式对象,就需要 递归 调用 reactive

function createReactiveObject(
  target: Target, // 用户传的对象
  isReadonly: boolean, // 是否只读
  baseHandlers: ProxyHandler<any>, // (普通对象或数组)的处理器函数
  collectionHandlers: ProxyHandler<any> // (Set、WeakSet、Map、WeakMap)的处理器函数
) {
  if (!isObject(target)) {
    // 如果用户传的不是对象就之间返回
    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
  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(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers // 普通的对象 Object 或 Array,处理器对象就使用 baseHandlers;如果是 Set, Map, WeakMap, WeakSet 中的一个,就使用 collectionHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

该核心函数主要做了以下事情:

  1. 若 target 不是对象,则直接返回 target
  2. 若 target 已经是 proxy 实例,则直接返回 target
  3. 若 target 不是一个可观察的对象,则直接返回 target
  4. 创建 proxy 实例
  • baseHandlers.ts 文件

该文件主要针对 4 种 proxy 实例定义不同的处理器

mutableHandlers: 完全响应式处理器
readonlyHandlers: 只读处理器
shallowReactiveHandlers: 浅层的响应式处理器
shallowReadonlyHandlers: 浅层的只读处理器
  • 完全响应式处理器
export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys,
}

mutableHandlers 处理器对五种操作做了拦截,分别是:

  1. get 获取属性
  2. set 设置属性
  3. deleteProperty 删除属性
  4. has 是否拥有某个属性
  5. ownKeys

重点:get、has、ownKeys 操作会做 track 依赖收集; set、deleteProperty 操作会 trigger 触发更新

get 属性的处理器对应的创建函数是 createGetter

const get = /*#__PURE__*/ createGetter()  // /*#__PURE__*/表示纯函数

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    if (key === ReactiveFlags.IS_REACTIVE) { // target 是否是响应式对象
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) { // target 是否是只读的
      return isReadonly
    } else if (
      key === ReactiveFlags.RAW &&
      receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
    ) { // 如果访问的 key 是 __v_raw,并且 receiver == target.__v_readonly || receiver == target.__v_reactive,则直接返回 target
      return target
    }

    const targetIsArray = isArray(target)

    // 如果 target 不是只读的是数组并且 key 属于三个方法之一 ['includes', 'indexOf', 'lastIndexOf'],即触发了这三个操作之一
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

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

    if (
      isSymbol(key)
        ? builtInSymbols.has(key as symbol)
        : key === `__proto__` || key === `__v_isRef`
    ) { // 如果 key 是 Symbol 并且属于 symbol 的内置方法之一,或者访问的是原型对象,直接返回结果
      return res
    }

    if (!isReadonly) { // 只读对象不收集依赖
      track(target, TrackOpTypes.GET, key)
    }

    if (shallow) { // 浅层响应立即返回,不递归调用 reactive()
      return res
    }

    // 如果是 ref 对象,则返回真正的值,即 ref.value,数组除外。
    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.
    // 由于 proxy 只能代理一层,所以 target[key] 的值如果是对象,就继续对其进行代理
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

需要注意以下几点:

  1. Reflect.get() 方法等价于 target[key], 有返回值
  2. 数组的处理
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
  return Reflect.get(arrayInstrumentations, key, receiver)
}


const arrayInstrumentations: Record<string, Function> = {}
// instrument identity-sensitive Array methods to account for possible reactive
// values
;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
  const method = Array.prototype[key] as any
  arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
    const arr = toRaw(this)
    // 对数组的每个值进行 track 操作,收集依赖
    for (let i = 0, l = this.length; i < l; i++) {
      track(arr, TrackOpTypes.GET, i + '')
    }
    // we run the method using the original args first (which may be reactive)
    const res = method.apply(arr, args)
    // 参数有可能是响应式的,函数执行后返回值为 -1 或 false,那就用参数的原始值再试一遍
    if (res === -1 || res === false) {
      // if that didn't work, run it again using raw values.
      return method.apply(arr, args.map(toRaw))
    } else {
      return res
    }
  }
})
// instrument length-altering mutation methods to avoid length being tracked
// which leads to infinite loops in some cases (#2137)
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
  const method = Array.prototype[key] as any
  arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
    pauseTracking()
    const res = method.apply(this, args)
    resetTracking()
    return res
  }
})
  1. builtInSymbols.has(key) 为 true 或原型对象不收集依赖
const builtInSymbols = new Set(
  Object.getOwnPropertyNames(Symbol)
    .map(key => (Symbol as any)[key])
    .filter(isSymbol)
)

set 属性的处理器对应的创建函数是 createSetter

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)
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value // 如果原来的值是 ref,但新的值不是,将新的值赋给 ref.value 即可
        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
    if (target === toRaw(receiver)) {
      if (!hadKey) { // 如果 target 没有 key,就代表是新增操作,需要触发依赖
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

deleteProperty、has、ownKeys

function deleteProperty(target: object, key: string | symbol): boolean {
  const hadKey = hasOwn(target, key)
  const oldValue = (target as any)[key]
  const result = Reflect.deleteProperty(target, key)
  if (result && hadKey) { // 如果删除结果为 true 并且 target 拥有这个 key 就触发依赖
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  return result
}

function has(target: object, key: string | symbol): boolean {
  const result = Reflect.has(target, key)
  if (!isSymbol(key) || !builtInSymbols.has(key)) {
    track(target, TrackOpTypes.HAS, key)
  }
  return result
}

function ownKeys(target: object): (string | number | symbol)[] {
  track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
  return Reflect.ownKeys(target)
}
  • effect.ts 文件
export function effect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
  if (isEffect(fn)) { // 如果已经是 effect 函数,取得原来的 fn
    fn = fn.raw
  }
  const effect = createReactiveEffect(fn, options)
  // 如果 lazy 为 false,马上执行一次
  // 计算属性的 lazy 为 true
  if (!options.lazy) {
    effect()
  }
  return effect
}

核心函数:createReactiveEffect

let uid = 0
function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect<T> {
  const effect = function reactiveEffect(): unknown {
    if (!effect.active) {
      return options.scheduler ? undefined : fn()
    }
    // 为了避免递归循环,所以要检测一下
    if (!effectStack.includes(effect)) {
      cleanup(effect)
      try {
        enableTracking()
        effectStack.push(effect)
        activeEffect = effect
        return fn()
      } finally {
        effectStack.pop()
        resetTracking()
        activeEffect = effectStack[effectStack.length - 1]
      }
    }
  } as ReactiveEffect
  effect.id = uid++
  effect.allowRecurse = !!options.allowRecurse
  effect._isEffect = true
  effect.active = true // 用于判断当前 effect 是否激活,有一个 stop() 来将它设为 false
  effect.raw = fn
  effect.deps = []
  effect.options = options
  return effect
}
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (!shouldTrack || activeEffect === undefined) {
    // activeEffect 为空,代表没有依赖,直接返回
    return
  }
  // targetMap 依赖管理中心,用于收集依赖和触发依赖
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  // targetMap 为每个 target 建立一个 map
  // 每个 target 的 key 对应着一个 dep
  // 然后用 dep 来收集依赖函数,当监听的 key 值发生变化时,触发 dep 中的依赖函数
  // 类似于这样
  // targetMap(weakmap) = {
  //     target1(map): {
  //       key1(dep): (fn1,fn2,fn3...)
  //       key2(dep): (fn1,fn2,fn3...)
  //     },
  //     target2(map): {
  //       key1(dep): (fn1,fn2,fn3...)
  //       key2(dep): (fn1,fn2,fn3...)
  //     },
  // }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect)
    activeEffect.deps.push(dep)
    if (__DEV__ && activeEffect.options.onTrack) {
      activeEffect.options.onTrack({
        effect: activeEffect,
        target,
        type,
        key,
      })
    }
  }
}

注意WeakMap 键是弱引用的 key 必须是一个对象,value 可以为任意值

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) {
    // 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) {
      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)) {
            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:
        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) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }

  effects.forEach(run)
}
  • operations.ts 文件
export const enum TrackOpTypes { // track 的类型
  GET = 'get', // get 操作
  HAS = 'has', // has 操作
  ITERATE = 'iterate' // ownKeys 操作
}

export const enum TriggerOpTypes { // trigger 的类型
  SET = 'set', // 设置操作,将旧值设置为新值
  ADD = 'add', // 新增操作,添加一个新的值 例如给对象新增一个值 数组的 push 操作
  DELETE = 'delete', // 删除操作 例如对象的 delete 操作,数组的 pop 操作
  CLEAR = 'clear' // 用于 Map 和 Set 的 clear 操作。
}

type 主要用于标识 track() 和 trigger() 的类型

Compiler 模板编译模块

Vue3.2 setup 语法

1. 变量、方法不需要 return 出来

<template>
  <div class="home">
    显示的值 {{flag }}
    <button @click="changeHandler">改变值</button>
  </div>
</template>
<!-- 只需要在script上添加setup -->
<script lang="ts" setup>
  import { ref } from 'vue'

  // flag变量不需要在 return出去了
  let flag = ref('开端-第一次循环')

  // 函数也可以直接引用,不用在return中返回
  let changeHandler = () => {
    flag.value = '开端-第二次循环'
  }
</script>

2. 组件不需要在注册

<!-- 这个是组件 -->
<template>
  <div>
    <h2>你好-我是肖鹤云</h2>
  </div>
</template>

使用的页面
<template>
  <div class="home">
    <test-com></test-com>
  </div>
</template>
<script lang="ts" setup>
  // 组件命名采用的是大驼峰,引入后不需要在注册
  // 在使用的使用直接是小写和横杠的方式连接 test-com
  import TestCom from '../components/TestCom.vue'
</script>

3. 新增 defineProps

<!-- 父组件 -->
<template>
  <div class="home">
    <test-com :info="msg" time="42分钟"></test-com>
  </div>
</template>
<script lang="ts" setup>
  // 组件命名采用的是大驼峰,引入后不需要在注册
  import TestCom from '../components/TestCom.vue'
  let msg = '公交车-第一次循环'
</script>

<!-- 子组件 -->
<template>
  <div>
    <h2>你好-我是汤小梦</h2>
    <p>信息:{{ info}}</p>
    <p>{{ time }}</p>
  </div>
</template>
<script lang="ts" setup>
  import { defineProps } from 'vue'
  defineProps({
    info: {
      type: String,
      default: '----',
    },
    time: {
      type: String,
      default: '0分钟',
    },
  })
</script>

4. 新增 defineEmits

<!-- 子组件 -->
<template>
  <div>
    <h2>你好-我是汤小梦</h2>
    <button @click="handler1Click">新增</button>
    <button @click="handler2Click">删除</button>
  </div>
</template>

<script lang="ts" setup>
  import { defineEmits } from 'vue'
  //  使用defineEmits创建名称,接受一个数组
  let $myEmit = defineEmits(['myAdd', 'myDel'])
  let handler1Click = () => {
    $myEmit('myAdd', '新增的数据')
  }

  let handler2Click = () => {
    $myEmit('myDel', '删除的数据')
  }
</script>

<!-- 父组件 -->
<template>
  <div class="home">
    <test-com @myAdd="myAddHandler" @myDel="myDelHandler"></test-com>
  </div>
</template>
<script lang="ts" setup>
  // 组件命名采用的是大驼峰,引入后不需要在注册,是不是爽歪歪呀!
  // 在使用的使用直接是小写和横杠的方式连接 test-com
  import TestCom from '../components/TestCom.vue'
  let myAddHandler = (mess): void => {
    console.log('新增==>', mess)
  }

  let myDelHandler = (mess): void => {
    console.log('删除==>', mess)
  }
</script>

5. 获取子组件中的属性值, defineExpose

<!-- 子组件 -->
<template>
  <div>
    <h2>你好-我是汤小梦</h2>
    <p>性别:{{ sex}}</p>
    <p>其他信息:{{ info}}</p>
  </div>
</template>

<script lang="ts" setup>
  import { reactive, ref, defineExpose } from 'vue'
  let sex = ref('男')
  let info = reactive({
    like: '喜欢小明',
    age: 28,
  })
  // 将组件中的属性暴露出去,这样父组件可以获取
  defineExpose({
    sex,
    info,
  })
</script>

<!-- 父组件 -->
<template>
  <div class="home">
    <button @click="getSonHandler">获取子组件中的数据</button>
  </div>
</template>
<script lang="ts" setup>
  import { ref } from 'vue'
  const testRef = ref()
  const getSonHandler = () => {
    console.log('获取子组件中的性别', testRef.value.sex)
    console.log('获取子组件中的其他信息', testRef.value.info)
  }
</script>

6. 新增指令 v-memo

该指令接收一个固定长度的数组作为依赖值进行[记忆比对]。如果数组中的每个值都和上次渲染的时候相同,则整个子树的更新会被跳过。即使是虚拟 DOM 的 VNode 创建也将被跳过,因为子树的记忆副本可以被重用。因此渲染的速度会非常的快。

7. style 新增 v-bind

<template>
  <span> 哈哈哈哈哈 </span>
</template>
<script setup>
  import { reactive } from 'vue'
  const state = reactive({
    color: 'red',
  })
</script>
<style scoped>
  span {
    /* 使用v-bind绑定state中的变量 */
    color: v-bind('state.color');
  }
</style>