Skip to content

Vue3.0 ​

img

vue3.0 初始化流程 ​

img

简单的实现

js
// vue3的初始化流程
const Vue = {
  createApp(options) {
    // 创建render, 此处可根据不同平台去创建不同的render
    const render = Vue.createRender({
      querySelector(select) {
        return document.querySelector(select)
      },
      insert(children, parent, anchor) {
        parent.insertBefore(children, anchor || null)
      }
    })
    return render.createApp(options)
  },
  createRender({ querySelector, insert }) {
    return {
      createApp(options) {
        return {
          // 挂载函数
          mount(selector) {
            const parent = querySelector(selector)
            if(!options.render) {
              options.render = this.compile(parent.innerHTML)
            }
            if(options.setup) {
              this.setupState = options.setup()
            } else {
              this.data = options.data()
            }

            // 确认render中数据从哪获取
            this.proxy = new Proxy(this, {
              get(target, key) {
                if (key in target.setupState) {
                  return target.setupState[key]
                } else {
                  return target.data[key]
                }
              },
              Set(target, key, val) {
                if (key in target.setupState) {
                  target.setupState[key] = val
                } else {
                  target.data[key] = val
                }
              }
            })

            const el = options.render.call(this.proxy)
            parent.innerHTML = ''
            // parent.appendChild(el)
            insert(el, parent)
          },
          // 编译函数,省略
          compile(template) {
            return function render() {
              const node = document.createElement("h3")
              node.textContent = this.title
              return node
            }
          }
        }
      }
    }
  }
}

步骤 ​

  • 挂载mount
    • 获取parent
  • 编译
    • compile
    • render
  • 兼容compositionApi
    • setup
  • 渲染,插入节点

数据响应式 ​

js
const isObject = val => val !== null && typeof val === 'object'

function reactive(obj) {
  if (!isObject(obj)) {
    return obj
  }
  // Proxy相当于在对象外层加拦截
  const observed = new Proxy(obj, {
    get(target, key, receiver) {
      // Reflect用于执行对象默认操作,更规范、更友好
      // Proxy和Object的方法Reflect都有对应
      const res = Reflect.get(target, key, receiver)
      console.log(`获取${key}:${res}`)
      // 依赖收集
      track(target, key)
      return isObject(res) ? reactive(res) : res
    },
    set(target, key, value, receiver) {
      const res = Reflect.set(target, key, value, receiver)
      console.log(`设置${key}:${value}`)
      trigger(target, key)
      return res
    },
    deleteProperty(target, key) {
      const res = Reflect.deleteProperty(target, key)
      console.log(`删除${key}:${res}`)
      trigger(target, key)
      return res
    }
  })
  return observed
}

const effectStack = []

function effect(fn) {
  const rxEffect = function() {
    // 1.捕获异常
    try {
      // 2.入栈
      effectStack.push(rxEffect)
      // 3.触发依赖收集
      return fn()
    } finally {
      // 4.出栈
      effectStack.pop()
    }
  }

  rxEffect()

  return rxEffect
}

// 依赖收集,建立target,key和上面的effect函数之间映射关系
// 需要一个数据结构存储该关系
// {target: {key: [cb1, cb2, ...]}}
let targetMap = new WeakMap()
function track(target, key) {
  // 获取effect存入的函数
  const effect = effectStack[effectStack.length - 1]

  if (effect) {
    // 获取target对应Map
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      depsMap = new Map()
      targetMap.set(target, depsMap)
    }
    // 获取depsMap中key和其值,也就是Set
    let deps = depsMap.get(key)
    if (!deps) {
      // 首次deps不存在,创建之
      deps = new Set()
      depsMap.set(key, deps)
    }

    // 将传入effect,添加到Set里面
    deps.add(effect)
    console.log(deps)
  }
}

function trigger(target, key) {
  // 获取映射关系
  const depsMap = targetMap.get(target)
  if (depsMap) {
    // 获取函数集合
    const deps = depsMap.get(key)
    deps.forEach(effect => {
      effect()
    })
  }
}

const state = reactive({
  foo: 'foo',
  bar: { a: 1 }
})

effect(() => {
  console.log('effect:', state.foo)
})

state.foo = 'fooooooo'

vue3.0的优化 ​

vue3.0带来的优化

代码组织 ​

  • vue3.0 代码结构

img

更好的代码管理方式:monorepo

Vue 2.x 的源码托管在 src 目录,然后依据功能拆分出了 compiler(模板编译的相关代码)、core(与平台无关的通用运行时代码)、platforms(平台专有代码)、server(服务端渲染的相关代码)、sfc(.vue 单文件解析相关代码)、shared(共享工具代码)

img

Vue 3.0 ,整个源码是通过 monorepo 的方式维护的,根据功能将不同的模块拆分到 packages

img

monorepo 把这些模块拆分到不同的 package 中,每个 package 有各自的 API、类型定义和测试。这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确。 另外一些 package(比如 reactivity 响应式库)是可以独立于 Vue.js 使用的,这样用户如果只想使用 Vue.js 3.0 的响应式能力,可以单独依赖这个响应式库而不用去依赖整个 Vue.js,减小了引用包的体积大小。

虚拟dom重写 ​

做了一些静态标记,针对不同类型对节点进行不同策略的处理,静态节点在 diff 过程中会直接跳过。

性能提升 ​

  1. 更好的初始性能
  2. tree-shaking 支持,更小的文件大小
  3. compositionAPI 新语法
  4. fragment, teleport, suspense 新组件
  5. 更好的 typescript 支持
  6. 自定义渲染器

tree-shaking 原理:依赖 ES2015 模块语法的静态结构(即 import 和 export),通过编译阶段的静态分析,找到没有引入的模块并打上标记。未被引入的 square 模块会被标记,然后压缩阶段会利用例如 uglify-js、terser 等压缩工具真正地删除这些没有用到的代码。 利用 tree-shaking 技术,如果在项目中没有引入 Transition、KeepAlive 等组件, 那么它们对应的代码就不会打包,这样也就间接达到了减少项目引入的 Vue.js 包体积的目的。

数据劫持优化 ​

  • vue1.x 与 vue2.x 是利用 Object.defineProperty 这个 API 去劫持数据的 getter 和 setter:
js
Object.defineProperty(data, 'a',{
  get() {
    // track
  },
  set() {
    // trigger
  }
})

这个 API 有一些缺陷,它必须预先知道要拦截的 key 是什么,它并不能检测对象属性的添加和删除。之前提供了 $set 和 $delete 实例方法,但是增加了一定对负担。

嵌套层级比较深的对象,需要递归遍历整个对象,执行 Object.defineProperty 把每一层对象数据都变成响应式的。

js
export default {
  data: {
    a: {
      b: {
        c: {
          d: 1
        }
      }
    }
  }
}
  • Vue.js 3.0 使用了 Proxy API 做数据劫持
js
const observed = new Proxy(data, {
  get() {
    // track
  },
  set() {
    // trigger
  }
})

Proxy 劫持的是整个对象,自然对于对象的属性的增加和删除都能检测到,Proxy API 也不能监听到内部深层次的对象变化,因此 Vue.js 3.0 的处理方式是在 getter 中去递归响应式,这样的好处是真正访问到的内部对象才会变成响应式。

Proxy 的优势

  • 可直接监听数组类型的数据变化
  • 监听的目标为对象本身,不需要像 Object.defineProperty 一样遍历每个属性,有一定的性能提升
  • 可拦截 apply、ownKeys、has 等13种对象方法,而 Object.defineProperty 不行
  • 直接实现对象属性的新增/删除

编译优化 ​

主要优化发生在 patch 阶段,也就是 diff 阶段。

  • vue2.0 的 diff 算法与模版的节点数量正相关,会造成大量的性能浪费。
  • vue3.0 的 diff 算法与模版的动态节点数正相关,避免了资源的浪费,其实现原理是通过 Block tree,打补丁时将跳过这些属性不会改变的节点。

重构 Virtual DOM ​

  • 模板编译时的优化,将一些静态节点编译成常量
  • slot 优化,将 slot 编译为 lazy 函数,将 slot 的渲染的决定权交给子组件
  • 模板中内联事件的提取并重用(原本每次渲染都重新生成内联函数)

语法 API 优化:CompositionAPI ​

  • 增强了逻辑复用
  • 增强了代码结构与可读性

取消组件的根节点 ​

由老的

js
<template>
  <div></div>
</template>

变成为

js
<template>
  <div></div>
  <div></div>
</template>

在做组件递归的时候可以减少 div 的生成

新特性 ​

setup ​

全局 import

vue2 中的 data, methods, computed 都是挂载在 this 上面,两个明显的缺点

  1. 不利于类型推导
  2. 没用到 computed 功能,代码也会被打包

vue3 的手动 import 更利于 Tree-shaking

ref ​

reactive 负责复杂数据结构,ref 可以把基本的数据结构包装成响应式

vue
<template>
  <div>
    <h1>{{state.count}} * 2 = {{double}}</h1>
    <h2>{{num}}</h2>
    <button @click="add"></button>
  </div>
</template>

<script>
import { reactive, computed, ref, method, onMounted } from 'vue'
export default {
  setup () {
    const state = reactive({
      count: 1
    })
    const num = ref(2)

    function add() {
      state.count++
      num += 20
    }

    const double = computed(() => state.count * 2)

    onMounted(( => {
      console.log('method')
    }))

    return {
      state,
      num,
      double,
      add
    }
  }
}
</script>

CompositionAPI 原理 ​

vue3.0 的 compositionApi 和 options 是共存的。

composition 和 options 如何做到和谐共存? ​

  • 关键代码

如果设置了 setup 就会先处理它,else 逻辑在后面做。如果设置了 setup 则先调用它,随后再处理别的 options。

img

setup 中的 this 指向 ​

setup 中的 this 就是它执行时的上下文,如果是 esm 方式打包,会是 undefined;如果是单文件的方式运行,会是 window;但是不管怎样都没有什么意义

Released under the MIT License.