# 第16章 关于 Vue 3.0

Vue 3.0 目前已经公开源代码了,感兴趣的小伙伴可以去https://github.com/vuejs/vue-next查看。关于Vue 3.0 相信大家都好奇很久了,之前的 RFC 讨论也是相当激烈。而这一版本在设计上的一些理念,这里会给大家介绍一下,大家可以在前面内容的基础上来进行渐进式学习,这与 Vue 框架的“渐进式框架”定位也比较相符。

# 16.1 Vue 3.0 兼容性

对于大多数开发者来说,尤其是经历过 Angular 断崖式升级的小伙伴们,对框架升级最担心的是兼容性。新能力大家当然想用,但对于项目和业务来说,稳定性是首位的,所以很多人会担心框架升级是否部分功能不可用。Vue 3.0 有不少新的想法和改动,但基本上兼容性大部分是没问题的。总体来看,除渲染函数 API 和scoped-slot语法之外,其余均保持不变或者将通过另外构建一个兼容包来兼容 2.x。

根据作者介绍的 Vue 3.0 计划中,以下是计划中的公共 API 更改:

  • 模板语法将保持 99%不变。scoped-slot语法可能会有一些小的调整,但是除此之外,没有计划更改模板的其他任何内容
  • API 在设计时考虑了 TypeScript 的类型推断,3.x 代码库本身将使用 TypeScript 编写,并提供增强的 TypeScript 支持。也就是说,在应用程序中使用 TypeScript 仍然是完全可选的
  • 顶层 API 可能会进行较大的调整,以避免在安装插件时会修改 Vue 的运行时。相反,插件的作用域会局限在组件树中
  • 函数性的组件将支持纯函数,但是将需要通过辅助函数来显式创建异步组件
  • 变化最大的部分是渲染功函数(render)能中使用的虚拟 DOM 的格式

也有人会好奇,是否 Vue 3.0 出来之后,之前对 Vue 的积累和理解都需要重新学习呢?其实不是这样的,Vue 3.0 是基于原有的框架设计上,引入了一些新的理念。代码是重构了,但是思想是演进的。可以理解为我们的项目跑了两三年之后,发现除了不少好用的新技术和能力,我们就要考虑是否要进行优化或是重构了。

# 16.2 设计目标

总的来说,Vue 3.0 的设计目标主要有以下几点:

  • 更小
  • 更快
  • 加强 API 设计一致性
  • 加强 Typescript 支持
  • 提高自身可维护性
  • 开放更多底层功能

我们一个个来看一下,Vue 3.0 的一些设计理念和实现方式调整,以及对开发者来说要怎样理解。以下内容基本上有了解 Vue 3.0 的开发者都已经从作者的分享、网上的 PPT 资源或是介绍说明中接触到,这里依然会先介绍一下。

# 16.2.1 更小

# Tree-shaking

什么是 Tree-shaking 呢?我们在进行项目开发的时候,经常会需要引入一些开源库。但是大多数时候我们都只会使用到这个开源库中的一小部分内容,Tree-shaking 用于描述移除 JavaScript 上下文中的未引用代码。也就是说,我们在打包代码的时候,会自动移除未用到的一些特性和功能代码,以此来减少生成的代码文件大小。

在 Vue 3.0 中,全局 API 和内置组件、功能都将支持 Tree-shaking。

# ~10kb gzipped

新的运行时,代码尺寸将控制在 10kb gzipped 上下。

# 16.2.2 更快

# Virtual DOM

传统 Virtual DOM 有以下性能瓶颈:
(1) 虽然 Vue 能够保证触发更新的组件最小化,但在单个组件内部依然需要遍历该组件的整个 Virtual DOM 树。
(2) 在一些组件整个模版内只有少量动态节点的情况下,这些遍历都是性能的浪费。
(3) 传统 Virtual DOM 的性能跟模版大小正相关,跟动态节点的数量无关。

在 Vue 3.0 中,Virtual DOM 会完全重写,通过动静结合的模式来进行突破,更新速度和内存占用均有质的提升,整体比 Vue2 性能提升 2-5 倍。具体实现方式包括:
(1) 通过模版静态分析生成更优化的 Virtual DOM 渲染函数,将模版切分为 block(if, for, slot)。
(2) 每个 block 内部动态节点位置是固定的,同时每个 block 的根节点会记录自己所包含的动态节点(包含子 block 的根节点)。
(3) 更新时只需要直接遍历动态节点,Virtual DOM 的更新性能与模版大小解耦,变为与动态节点的数量相关。

这里可以简单理解为,Virtual DOM 的更新从以前的整体作用域调整为树状作用域,树状的结构会带来算法的简化以及性能的提升。

# 观察者机制

Proxy 对象是什么呢,它用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等),通过new Proxy(target, handler)来创建,参数说明如下:

表 16-1 new Proxy()参数说明

参数 说明
target 用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
handler 一个对象,其属性是当执行一个操作时定义代理的行为的函数

同时handler参数中,常用的方法包括:

  • handler.get(): 用于拦截对象的读取属性操作
  • handler.set(): 用于拦截设置属性值的操作
  • handler.getPrototypeOf(): 当读取代理对象的原型时,该方法就会被调用
  • handler.has(): 针对in操作符的代理方法

我们能看到,当我们给某个对象添加了代理之后,就可以改变一些原有的行为,或是通过钩子的方式添加一些处理,我们用来触发界面更新、其他数据更新等也是可以的。

相比于之前的 getter/setter 监控数据变更,Vue 3.0 将会是基于 Proxy 的变动侦测,通过代理的方式来监控变动,整体性能会得到优化。同时从长远来看,JS 引擎会持续优化 Proxy(getter/setter 基本不会变化)。

# 编译时优化

编译器架构重构,更多编译时(compile-time)会得到优化,以减少 runtime 开销。

# 16.2.3 加强 API 设计一致性

Vue 3.0 整体使用 Function-based API,对比 Class API,拥有以下优势:

  • 更灵活的逻辑复用能力
  • 更好的 TypeScript 类型推导支持
  • 更好的性能
  • Tree-shaking 友好
  • 代码更容易被压缩

# 16.2.4 加强 Typescript 支持

Vue 3.0 本身用 TypeScript 重写,内置 typing,支持 TSX,同时不会影响不使用 TS 的用户。

# 16.2.5 开放更多底层功能

底层 API 会更多地开放给开发者使用,例如自定义渲染能力:

import { createRenderer } from "@vue/runtime-core";
const { render } = createRenderer({
  nodeOps,
  patchData
});

以上内容,在之前的一些 Vue 分享会中也有相关说明。如今相关的源代码也已经出来了,虽然还是 Pre Alpha 阶段,但我们可以通过这部分源码来结合理解一下 Vue 3.0 是进行了怎样的进化。

# 16.3 Vue 3.0 Pre Alpha

2019 年 10 月 5 日,Vue 3.0 Pre Alpha 阶段的源代码(https://github.com/vuejs/vue-next)已经公开了。我们来看一下Vue 3.0 的设计是否都实现了,大概又是怎样实现的。

# 16.3.1 代码 README 说明

我们去看源代码,通常会不知道如何下手。即使是代码注释比较充分,但是数量的确是很多,要怎么从中理清楚主要的逻辑和思路呢?一般来说我们可以从 README 开始,例如我们来看看 Vue 3.0 Pre Alpha 的说明,这里主要讲了两块的内容:编译器 Compiler 和运行时 Runtime

其实前面宣传和分享的时候,给大家介绍的更多是目标,而源代码能更好地表达如何实现。显然,对于更快、更小、对开发者更友好等种种目标,Vue 3.0 是从编译器和运行时两大块来着手处理实现的。

# 编译器 Compiler

关于编译器,代码说明是包括以下调整:

  • 模块化架构
  • "Block tree"优化
  • 更激进的 static tree hoisting 功能
  • Source Map 支持
  • 内置标识符前缀(又名“stripWith”,它会删除生成的渲染函数中的with用法,使它们兼容严格模式)
  • 内置整齐打印(pretty-print)功能
  • 移除 Source Map 和标识符前缀后,精简至 10kb 左右大小的 brotli 压缩的浏览器版本

# 运行时 Runtime

关于运行时,包括以下的优化内容:

  • 速度显著提升
  • 同时支持 Composition API 和 Options API,并支持 typings
  • 基于 Proxy 的变更检测
  • 支持 Fragments、Portals、Suspense w/ async setup()

# 2.x 功能迁移

在该 Pre Alpha 阶段代码里,还有一些 2.x 的功能尚未完成:

  • 服务器端渲染
  • <keep-alive>
  • <transition>
  • 编译器对 DOM 特定修饰符的转换,包括v-onv-modelv-textv-prev-oncev-htmlv-show等,部分已完成

此外,该版本的实现还需要运行时环境中的本机 ES2015+,且尚未支持 IE11。

# 16.3.2 源码欣赏

我们下载了源代码之后,可以看到有以下的 packages:
image

表 16-2 Vue 3.0 Pre Alpha 源码 packages

packages 说明
compiler-core 编译器核心,包括将模板转成 AST、生成 JS 逻辑等功能
compiler-dom 编译器 DOM 补充部分,与浏览器相关的模板编译和逻辑处理等功能
reactivity 数据相关的系统,包括数据监控(Proxy 代理)、计算和响应等基础能力
runtime-core 运行时核心,包括 Vue 组件系统、Vue 的各种 API 实现、虚拟 DOM 相关,同时暴露了更多的底层能力如自定义渲染器
runtime-dom 运行时 DOM 相关补充,包括处理原生 DOM API、DOM 事件和 DOM 属性等
runtime-test 用于测试的运行时,该运行时基于虚拟 DOM 的 JS 对象,可以用在所有 JS 环境里
server-renderer SSR 服务端渲染
shared 包含一些通用的配置和方法
template-explorer 模板编译输出,方便调试
vue 用于构建完整版本的 Vue,使用了编译器和运行时

我们看到完整版本的 Vue 里引用了 compiler-dom 和 runtime-dom,但这两者其实还依赖了 compiler-core、runtime-core、reactivity,也就是最终完整的的 Vue 3.0 Pre Alpha 版本了。

# @vue/complier-core

翻开源码,我们其实能看到和 Vue2.x 相比一个很大的改变:整体的功能代码进行了解耦,每个功能模块都清晰独立地抽离出来了。还可以开放更多的底层能力给到开发者。

Vue 3.0 整体使用 Function-based API,同时使用 Typescript 进行开发。除了可读性、维护性上的提升,之前提到的一些优势也都体现出来了:模块化解耦获得了更灵活的逻辑复用能力、Tree-shaking、Source Map 支持更加友好,开发者也获得更好的 TypeScript 类型推导支持。

以 complier-core 中 index.ts 中的代码为例:

// @vue/compiler-core/src/index.ts
// 函数式 API 可便捷地对逻辑进行解耦和复用
// 能看到 complier-core 中功能主要包括模板解析、转成AST、生成JS逻辑等一些功能
import { parse, ParserOptions } from "./parse";
import { transform, TransformOptions } from "./transform";
import { generate, CodegenOptions, CodegenResult } from "./codegen";
import { RootNode } from "./ast";
import { isString } from "@vue/shared";
import { transformIf } from "./transforms/vIf";
import { transformFor } from "./transforms/vFor";
import { transformExpression } from "./transforms/transformExpression";
import { transformSlotOutlet } from "./transforms/transformSlotOutlet";
import { transformElement } from "./transforms/transformElement";
import { transformOn } from "./transforms/vOn";
import { transformBind } from "./transforms/vBind";
import { defaultOnError, createCompilerError, ErrorCodes } from "./errors";
import { trackSlotScopes, trackVForSlotScopes } from "./transforms/vSlot";
import { optimizeText } from "./transforms/optimizeText";
import { transformOnce } from "./transforms/vOnce";
import { transformModel } from "./transforms/vModel";

export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions;

// 我们将其命名为“baseCompile”,以便更高级别的编译器(例如 @vue/compiler-dom)
// 可以在重新导出其他所有内容的同时导出“compile”
export function baseCompile(
  template: string | RootNode,
  options: CompilerOptions = {}
): CodegenResult {
  // 省略一部分

  // 这里先解析模板,然后生成AST对象
  const ast = isString(template) ? parse(template, options) : template;

  const prefixIdentifiers =
    !__BROWSER__ &&
    (options.prefixIdentifiers === true || options.mode === "module");

  // 对每个AST对象节点进行处理
  transform(ast, {
    ...options,
    prefixIdentifiers,
    nodeTransforms: [
      transformIf,
      transformFor,
      ...(prefixIdentifiers
        ? [
            // 此处顺序很重要
            trackVForSlotScopes,
            transformExpression
          ]
        : []),
      trackSlotScopes,
      transformSlotOutlet,
      transformElement,
      optimizeText,
      // 开放给使用者添加的一些处理逻辑
      // 例如 compiler-dom 中新增了对 DOM 的一些处理
      ...(options.nodeTransforms || [])
    ],
    directiveTransforms: {
      on: transformOn,
      bind: transformBind,
      once: transformOnce,
      model: transformModel,
      // 开放给使用者添加的一些处理逻辑
      ...(options.directiveTransforms || {})
    }
  });

  // 将AST对象生成VNode、JS逻辑等
  return generate(ast, {
    ...options, // 开放给使用者添加的一些转换逻辑
    prefixIdentifiers
  });
}

从上面的代码,我们能看到,模板解析生成 AST 之后,还会根据 AST 对象来进行转换。而 Vue 3.0 中提到的 Virtual DOM 优化的内容也在这里进行,通过模版静态分析生成更优化的 Virtual DOM 渲染函数,将模版切分为 block(if, for, slot),来提升 Virtual DOM 的性能。例如对某些语法进行“block”分块,然后整个应用以树状将每个“block”串起来,这样在更新、数据对比监控的时候,都可以从上而下地进行,也可以将变更控制在更小的范围,从而提升性能。

其他的功能设计也跟这里比较相似,通过函数式 API 来解耦基础能力,同时留空一些可补充的能力。功能拆解后,不管是组装的灵活性,还是可测试、易拓展、Tree-shaking 等,都有质的提升。

# @vue/complier-dom

同样地,要了解这个模块主要是用来做什么,我们也需要首先打开 index.ts 文件:

// @vue/compiler-dom/src/index.ts
// 依赖了@vue/compiler-core
import {
  baseCompile,
  CompilerOptions,
  CodegenResult
} from "@vue/compiler-core";
// 与 DOM 相关的一些解析和转换
import { parserOptionsMinimal } from "./parserOptionsMinimal";
import { parserOptionsStandard } from "./parserOptionsStandard";
import { transformStyle } from "./transforms/transformStyle";
import { transformCloak } from "./transforms/vCloak";
import { transformVHtml } from "./transforms/vHtml";
import { transformVText } from "./transforms/vText";
import { transformModel } from "./transforms/vModel";

// 导出补充了DOM相关的“compile”模块
export function compile(
  template: string,
  options: CompilerOptions = {}
): CodegenResult {
  // 基于 @vue/compiler-core 中的 baseCompile 进行补充拓展
  // 从而导出一个完整的 Vue 3.0 版本的编译器
  return baseCompile(template, {
    ...options,
    ...(__BROWSER__ ? parserOptionsMinimal : parserOptionsStandard),
    nodeTransforms: [transformStyle, ...(options.nodeTransforms || [])],
    directiveTransforms: {
      cloak: transformCloak,
      html: transformVHtml,
      text: transformVText,
      model: transformModel, // 此处重写了 compiler-core 的 vModel 转换
      ...(options.directiveTransforms || {})
    }
  });
}
export * from "@vue/compiler-core";

显然,compiler-dom 模块是基于 compiler-core 模块补齐 DOM 相关能力,我们可以理解为运行在浏览器中需要的一些能力补齐。这里我们能看到,complier-dom 中重写了 compiler-core 的 vModel 转换,因为在 Vue 中v-model只能用在表单或是自定义表单的组件,我们来看一下实现:

// @vue/compiler-dom/src/transforms/vModel.ts
export const transformModel: DirectiveTransform = (dir, node, context) => {
  const res = baseTransform(dir, node, context);
  const { tag, tagType } = node;
  if (tagType === ElementTypes.ELEMENT) {
    // 其他省略

    // 显然,这里对原生的表单组件进行处理
    if (tag === "input" || tag === "textarea" || tag === "select") {
      let directiveToUse = V_MODEL_TEXT;
      if (tag === "input") {
        const type = findProp(node, `type`);
        if (type) {
          if (type.type === NodeTypes.DIRECTIVE) {
            // :type="foo"
            directiveToUse = V_MODEL_DYNAMIC;
          } else if (type.value) {
            switch (type.value.content) {
              case "radio":
                directiveToUse = V_MODEL_RADIO;
                break;
              case "checkbox":
                directiveToUse = V_MODEL_CHECKBOX;
                break;
            }
          }
        }
      } else if (tag === "select") {
        directiveToUse = V_MODEL_SELECT;
      } // 通过 needRuntime 返回辅助符号 // 导入将替换 resovleDirective 调用
      // 注入运行时指令
      res.needRuntime = context.helper(directiveToUse);
    } else {
      // 报错,此处省略
    }
  }
  return res;
};

其实在 Vue 中,除了原生表单组件 input、textarea、select 这些,还有一些自定义组件也需要进行处理的,这里也需要补齐对自定义组件的处理,这部分我们会在 runtime-dom 运行时 DOM 补充的部分能看到。

# @vue/reactivity

该模块实现了一个数据管理模块,它可以单独使用,也可以与框架配合使用。我们能看到 Vue 3.0 的数据监控的优化亮点都包含在这个模块了,这个模块主要包括了:

  • baseHandlers/collectionHandlers: 处理器,用作 Proxy 的处理器
  • computed: 计算属性部分
  • effect: 一些辅助能力,例如跟踪依赖项、触发一些检测、响应等等
  • reactive: 响应式数据,基于 Proxy 代理实现
  • ref: 数据容器,在 Vue 中是指向组件实例的引用

我们可以看看创建响应式数据的部分:

// @vue/reactivity/src/reactive.ts
// 创建响应式数据
function createReactiveObject(
  target: any,
  toProxy: WeakMap<any, any>,
  toRaw: WeakMap<any, any>,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`);
    }
    return target;
  }
  // 目标已经有相应的代理
  let observed = toProxy.get(target);
  if (observed !== void 0) {
    return observed;
  }
  // 目标已经是一个代理
  if (toRaw.has(target)) {
    return target;
  }
  // 只有在白名单里的值可被观察
  if (!canObserve(target)) {
    return target;
  }
  // 获取集合或是非集合类型的处理器
  const handlers = collectionTypes.has(target.constructor)
    ? collectionHandlers
    : baseHandlers;
  // 创建代理,并记录源对象和代理对象的关系
  observed = new Proxy(target, handlers);
  toProxy.set(target, observed);
  toRaw.set(observed, target);
  // 存储{target-> key-> dep}关系
  if (!targetMap.has(target)) {
    targetMap.set(target, new Map());
  }
  return observed;
}

我们已经可以看到,通过 Proxy 的方式创建一个响应式数据,并将数据之间的依赖关系、源数据与代理对象之间的关系都记录下来,当我们更新数据的时候,就可以根据依赖进行范围内的检查更新:

// @vue/reactivity/src/baseHandlers.ts
// 这是响应式数据 set 的时候代理处理函数
function set(
  target: any,
  key: string | symbol,
  value: any,
  receiver: any
): boolean {
  // 获取代理对象数据
  value = toRaw(value);
  // 获取原对象数据
  const hadKey = hasOwn(target, key);
  const oldValue = target[key];
  if (isRef(oldValue) && !isRef(value)) {
    oldValue.value = value;
    return true;
  }
  // 采用 Reflect.set 方法将值赋值给对象的属性
  // 确保完成原有的行为,然后再部署额外的功能
  const result = Reflect.set(target, key, value, receiver);
  // 如果目标是原始原型链中的某个对象,请勿触发
  // 可防止循环触发
  if (target === toRaw(receiver)) {
    // 计算数据变更方式(更新或新增),并进行变更触发
    // trigger 会根据依赖关系,对 computed 和需要连带更新的数据进行更新
    if (__DEV__) {
      const extraInfo = { oldValue, newValue: value };
      if (!hadKey) {
        trigger(target, OperationTypes.ADD, key, extraInfo);
      } else if (value !== oldValue) {
        trigger(target, OperationTypes.SET, key, extraInfo);
      }
    } else {
      if (!hadKey) {
        trigger(target, OperationTypes.ADD, key);
      } else if (value !== oldValue) {
        trigger(target, OperationTypes.SET, key);
      }
    }
  }
  return result;
}

我们能看到,使用了 Proxy 代理+依赖关系链的方式来进行数据检测和变更更新,同时结合运行时 DOM 相关补充,就可以完成对页面的局部刷新。

# @vue/runtime-core

运行时核心模块主要包括了组件系统和 Vue 的 API 实现、虚拟 DOM 相关的功能,我们同样地可以从 index.ts 文件中看到:

// @vue/runtime-core/src/index.ts
// 公共API部分------------
export { createComponent } from "./apiCreateComponent";
export { nextTick } from "./scheduler";
export * from "./apiReactivity";
export * from "./apiWatch";
export * from "./apiLifecycle";
export * from "./apiInject";

// 高级API部分--------------
// 需要进行原始渲染功能的用户可以使用
export { h } from "./h";
// VNode(虚拟DOM)相关能力
export {
  createVNode,
  cloneVNode,
  mergeProps,
  openBlock,
  createBlock
} from "./vnode";
// VNode类型的symbols
export { Text, Comment, Fragment, Portal, Suspense } from "./vnode";
// VNode的标志
export { PublicShapeFlags as ShapeFlags } from "./shapeFlags";
export { PublicPatchFlags as PatchFlags } from "@vue/shared";

// 可用于高级插件
export { getCurrentInstance } from "./component";

// 可用于自定义渲染
export { createRenderer } from "./createRenderer";
export { warn } from "./warning";
export {
  handleError,
  callWithErrorHandling,
  callWithAsyncErrorHandling
} from "./errorHandling";

// 内部API,用于编译器生成的代码
// 应该与'@vue/compiler-core/src/runtimeConstants.ts'同步
export { applyDirectives } from "./directives";
export { resolveComponent, resolveDirective } from "./helpers/resolveAssets";
export { renderList } from "./helpers/renderList";
export { toString } from "./helpers/toString";
export { toHandlers } from "./helpers/toHandlers";
export { renderSlot } from "./helpers/renderSlot";
export { createSlots } from "./helpers/createSlots";
export { capitalize, camelize } from "@vue/shared";

// 内部API,用于与运行时编译器集成
export { registerRuntimeCompiler } from "./component";

我们能看到,runtime-core 暴露了一些底层 API 能力,例如自定义渲染等,开发者可以基于这些基础能力之上,补充平台相关能力,打造一个完整的运行时。像@vue/runtime-dom 就时补充了 DOM 相关的运行时能力,从而构成完整的 Vue 运行时功能模块。

# @vue/runtime-dom

runtime-dom 模块是基于 runtime-core 开发的浏览器上的运行时,主要补齐了浏览器环境下 DOM 节点和节点属性的一些渲染和更新能力。

怎么补齐呢?我们看一个例子:

// @vue/runtime-dom/src/nodeOps.ts
// 对一些 VNode 操作,增加更新到页面的 DOM 节点操作

// 获取 document 对象
const doc = document;

export const nodeOps = {
  insert: (child: Node, parent: Node, anchor?: Node) => {
    if (anchor != null) {
      parent.insertBefore(child, anchor);
    } else {
      parent.appendChild(child);
    }
  },

  remove: (child: Node) => {
    const parent = child.parentNode;
    if (parent != null) {
      parent.removeChild(child);
    }
  },

  createElement: (tag: string, isSVG?: boolean): Element =>
    isSVG ? doc.createElementNS(svgNS, tag) : doc.createElement(tag),

  querySelector: (selector: string): Element | null =>
    doc.querySelector(selector)

  // 篇幅原因,省略一部分内容
};

我们可以通过runtime-core提供的自定义渲染能力,把 DOM 节点增删改查的能力添加进去:

// @vue/runtime-dom/src/index.ts
import { createRenderer } from '@vue/runtime-core'
import { nodeOps } from './nodeOps'
import { patchProp } from './patchProp'

const { render, createApp } = createRenderer<Node, Element>({
  patchProp,
  ...nodeOps
})

export { render, createApp }

所以在 Vue 3.0 版本中,可以通过 render 和 createApp 这两个 API 来进行初始化项目和页面渲染。

# 其他模块

对于其他模块(runtime-test、server-renderer、shared、template-explorer、vue),前面也有简单介绍,篇幅关系就不再一个个描述了。

回顾一下 Vue 3.0 的目标(更小、更快、加强 API 设计一致性、加强 Typescript 支持、提高自身可维护性、开放更多底层功能),其实基本上都达成了。

重构是一件很花精力、考验团队能力的事情,尤其是在 Vue 本身就很受欢迎的情况下,依然决定要进行重构。虽然偶尔会有些“学不动”的捣乱声音,但作为技术人,该有的追求还是要有的,如今互联网世界日新月异,止步不前只会面临被淘汰。而 Vue 3.0 的这一番举动,个人觉得真的是很棒的一个决定。