Vue3 + TypeScript 系列一 - Vue3 初体验

420 阅读20分钟

一、Vue3 初体验.png

1. 认识 Vue.js

1.1 为什么要学习 Vue

如果从现实(找工作)的角度出发,学好 Vue 你可以找到一份满意的前端工作,而没有掌握 Vue 则很难找到一份满意的前端工作(需要补充的是,除了 Vue,现在不少岗位对“小程序”也有要求)。

1.2 Vue 的特点

Vue(读音 /vjuː/,类似于 view)是一套用于构建用户界面的渐进式 JavaScript 框架:

  • 全称是 Vue.jsVuejs
  • 什么是渐进式框架呢?
    • 渐进式框架意味着我们可以在项目中一点点地引入和使用 Vue,而不一定需要全部使用 Vue 来开发整个项目。你可以选择使用(或不使用) Vue 的某部分功能,你也可以只在项目的某个模块中使用 Vue

1.3 VueReact 等框架的对比

目前前端最流行的三大框架:VueReactAngular

  • Angular:入门门槛较高,并且国内市场占有率较低。但不否认其本身是非常优秀的框架,它的很多设计思想与 Node 框架 nest.js 相似,而且它们基本上都要用 TypeScript 开发。
  • React:国内外的市场占有率都较高。作为前端工程师也是必须学习的一个框架。大公司大项目在以前也更倾向于使用 React 开发,因为相对来说 React 可能更加的灵活,一些 Vue2 可能没法做的场景 React则可以做(当然,现在 Vue2 版本升级到 3 版本后,有了 Composition API,也非常灵活了)。
  • Vue:在国内市场占有率最高,几乎所有的前端岗位都会对 Vue 有要求。

1.3.1 框架数据对比

1.3.1.1 Google 指数

框架数据对比-Google 指数

1.3.1.2 百度指数

框架数据对比-百度指数

1.3.1.3 npm 下载量

框架数据对比-npm 下载量

1.3.1.4 GitHub

框架数据对比-GitHub

1.3.2 谁是最好的前端框架

这里不给出结论,因为就像人们争论谁是世界上最好的语言一样,争论这个问题没有意义。

但我们不妨从现实的角度先分析一下,学习哪门语言更容易找工作(哪门语言市场占有率更高)?

  • 找后端的工作:优先推荐 Java,其次是 Go,再次是 NodeJavaScript),可能不推荐 PHPC#
  • 找前端的工作:优先推荐 JavaScriptTypeScript),其次是 Flutter,再次是 AndroidJavaKotlin)、iOSOCSwift);
  • 其它方向:算法工程师、游戏开发、人工智能等等。

那么,就前端来说,学习了 HTMLCSSJavaScript 之后,哪一个框架更容易找到工作?

  • 如果去国外找工作,优先推荐 React,其次是 VueAngular,不推荐 jQuery 了;
  • 如果在国内找工作,优先推荐、必须学习 Vue,其次是 React,再次是 Angular,不推荐 jQuery 了。

1.4 学习 Vue2 还是 Vue3

  • 对于已经掌握 Vue2 的同学来说,直接学习 Vue3 中相关的内容即可;
  • 对于没有学过 Vue2 的同学,直接学习 Vue3 就行了。

《程序员》:Vue 3 版本兼容 2.x,对于想要学习 Vue 的开发者而言,时常在纠结是从 Vue2 开始学基础还是直接学 Vue3,对此,你有着什么样的建议?

尤雨溪(Vue 作者):直接学 Vue 3就行了,基础概念是一模一样的。

1.5 目前需要学习 Vue3

2020918 日,万众期待的 Vue3 终于发布了正式版,命名为 “One Piece”。

  • 它带来了很多新的特性:更小的体积更好的性能更优秀的 API 设计更好的 TypeScript 集成
  • Vue3 刚刚发布时,很多人跃跃欲试,想要尝试 Vue3 的各种新特性,这时我们用 Vue3 写写 demo 练习是没有问题的,但真正在实际业务项目中使用 Vue3 还需要一个相对过程;
  • 包括 Vue3 的进一步稳定、社区更多 Vue3 相关的插件、组件库的支持和完善(因为生态需要时间,生态里的工具、周边以及库都需要时间去兼容,Vue3 的一些新用法也需要时间去沉淀)。

那么现在是否是学习 Vue3 的时间呢?

  • 答案是肯定的
  • 首先 Vue3 在经过一系列的更新和维护后,已经趋于稳定,并且尤雨溪之前在 VueConf China 2021 上也宣布会在今年(2021)第二季度Vue3 作为默认版本(现在的时间是 710 号,npm 还没有默认安装 Vue3,文档也还没有默认指向 v3 文档)。
  • 社区经过一段时间的沉淀后,也更加完善了,包括 Ant Design VueElement Plus 都提供了对 Vue3 的支持,所以很多公司目前新的项目都已经在使用 Vue3 来进行开发了。
  • 并且在面试中,也会问到 Vue3Vite2 工具相关的问题。

1.6 Vue3 带来的变化

1.6.1 源码

  • 源码通过 monorepo 的形式来管理
    • mono:单个;
    • reporepository(仓库);
    • 就是将许多项目的代码存储在同一个仓库中;
    • 这样做可以使得多个包本身相互独立,可以有自己的功能逻辑、单元测试等,同时又在同一个仓库下,方便管理;
    • 而且模块划分得更加清晰,可维护性、可扩展性更强。
  • 源码使用 TypeScript 进行了重写
    • Vue2.x 时,Vue 使用 Flow 进行类型检测;
    • 现在到了 Vue3.xVue 的源码全部使用 TypeScript 进行重构,并且 Vue 本身对 TypeScript 的支持也更好了。

1.6.2 性能

  • 使用 Proxy 进行数据劫持
    • Vue2.x 的时候,Vue2 是使用 Object.defineProperty 来劫持数据的 gettersetter 方法的;
    • 这种方式一直存在一个缺陷:当给对象添加或删除属性时,无法劫持和监听;
    • 所以在 Vue2.x 的时候,不得不提供一些特殊的 API,比如 $set$delete,事实上都是一些 hack 方法,增加了开发者学习新的 API 的成本;
    • 而在 Vue3.x 开始,Vue 使用 Proxy 来实现数据的劫持,该 API 的用法和相关原理后续会讲到;
  • 删除了一些不必要的 API
    • 移除了实例上的 $on$off$once
    • 移除了一些特性:filters(过滤器)、inline template attribute(内联模板属性)等;
  • 编译方面的优化
    • 生成 Block Tree
    • Slot 编译优化;
    • diff 算法优化;

1.6.3 新的 API

  • Options APIComposition API
    • Vue2.x 的时候,我们会通过 Options API 来描述组件对象;
    • Options API 包括 propsdatacomputedwatch、生命周期事件、methods 等等选项;
    • Options API 存在比较大的问题是多个逻辑可能是在不同的地方:
      • 比如 created 中会使用 methods 中的某个方法来修改 data 中的数据,代码的内聚性非常差;
    • Composition API 可以将相关联的代码放到同一处进行处理,而不需要分散在多个 Options 中,也就不用再在多个 Options 之间寻找了;
  • Hooks 函数增加代码的复用性
    • Vue2.x 的时候,我们通常通过 mixins 在多个组件之间共享逻辑;
    • 但是有一个很大的缺陷就是 mixins 也是由一大堆的 Options 组成的,多个 mixins 间会存在命名冲突的问题;
    • 到了 Vue3.x,我们可以通过 Hook 函数将一部分独立的逻辑抽取出去,并且它们还可以做到是响应式的;
    • 具体的好处,会在后续演练和讲解(包括原理);

2. Vue 的安装

简单认识了 Vue 之后,如何使用它呢?

首先,我们要知道,Vue 的本质就是一个 JavaScript 库。刚开始我们不需要把它想象的非常复杂,只需把它理解成一个已经帮助我们封装好的库,在项目中我们可以引入并使用它。

使用 Vue 之前,我们需要先安装 Vue,安装 Vue 这个 JavaScript 库的方式有以下 4 种:

  • 在页面中通过 CDN 的方式引入;
  • 下载 VueJavaScript 文件后在页面中手动引入;
  • 通过 npm 包管理工具安装(讲 Webapck 时再讲);
  • 直接安装 Vue CLI,安装 Vue CLI 时会安装 Vue(讲完 Webpack 后讲脚手架时再讲,后续我们通过 Vue CLI 创建项目并使用 Vue);

2.1 方式一、CDN 引入

  • 什么是 CDN 呢?CDN 即内容分发网络(Content Delivery NetworkContent Distribution Network,缩写:CDN
    • 它是指通过相互连接的网络系统,利用最靠近每位用户的服务器;
    • 更快、更可靠地将音乐、图片、视频、应用程序及其它文件发送给用户;
    • 来提供高性能、可扩展性及低成本的网络内容传递给用户。

未使用 CDN 服务器

使用 CDN 服务器

  • 常用的 CDN 服务器可以分为两种:
    • 自己的 CDN 服务器:需要购买自己的 CDN 服务器,目前华为、阿里、腾讯、亚马逊、谷歌等平台上都可以购买 CDN 服务器;
    • 开源的 CDN 服务器:国际上使用比较多的是 unpkgJSDelivrcdnjs
  • 通过 CDN 引入 Vue 的最新版本:
<script src="https://unpkg.com/vue@next"></script>

在浏览器中访问 https://unpkg.com/vue@next 这一 CDN 地址也可以看到 Vue 打包后没有经过压缩的源代码(在开发阶段建议用没有经过压缩的代码,这样可以看到更多细节):

通过 CDN 引入的 Vue 源码

可以看到,该文件的最上面定义了一个全局变量 Vue(其实是一个对象),因此,我们接下来就是通过这个全局变量 Vue 来使用 Vue 了。

  • Hello Vue 案例的实现:
<!-- 4. 用来给 app 对象挂载的元素(通常为 div,并且设置 id 为 app,但也可以使用其它元素和其它
选择器) -->
<div id="app"></div>

<!-- 1. CDN 引入 Vue -->
<script src="https://unpkg.com/vue@next"></script>
<script>
  const obj = {
    // 3. 在 template 属性中声明后续 createApp 函数返回的 app 对象中要显示的内容
    template: '<h2>Hello Vue</h2>'
  };
  // 2. 调用全局变量 Vue 中的 createApp 函数,
  // 该函数要求传入一个对象,而其返回值也是一个对象
  const app = Vue.createApp(obj);
  // 5. 将 app 对象挂载到 id 为 app 的 HTML 元素上(即告诉 app 对象要在哪里显示)
  // 这里我们不需要通过 document.querySelector 方法手动去拿目标元素,只需要传入要匹配的选择器的 
  // DOM 字符串即可,因为 Vue 内部会根据这里传入的字符串帮助我们用 document.querySelector 去找到
  // 目标元素(源码中对应的代码:packages/runtime-dom/src/index.ts -> createApp -> app.mount -> 
  // normalizeContainer(containerOrSelector) -> if (isString(container)) -> 
  // document.querySelector(container))
  app.mount('#app');
</script>

CDN 引入并使用 Vue

总结一下上述案例使用 Vue5 个步骤:

  1. 引入 Vue
  2. 创建一个包含模板的对象;
  3. 通过全局变量 Vue 调用 createApp 方法(对象中的函数称之为方法)并传入该对象;
  4. 返回的对象调用 mount 方法把自己挂载到某个元素上;
  5. 页面上显示出模板内容;

2.2 方式二、下载后引入

下载 Vue 的源码,可以直接打开 CDN 的链接:

  • 打开链接(unpkg.com/vue@next ),复制其中所有的代码;
  • 创建一个新的文件,如 vue.js,将复制的代码粘贴进该文件中。

通过 CDN 链接获取 Vue 源码

接下来,就可以(比如在“本地引入.html”中)通过 script 标签引入刚才创建的新文件:

<script src="./js/vue.js"></script>
  • 你好啊,Vue3 案例的实现:
<div id="app"></div>

<script src="./js/vue.js"></script>
<script>
  Vue.createApp({
    template: '<h2>你好啊,Vue3</h2>'
  }).mount('#app');
</script>

下载后引入并使用 Vue

3. 计数器案例

下面我们来实现一个计数器的案例:

  • 点击 +1 按钮,页面内容会显示数字 +1
  • 点击 -1 按钮,页面内容会显示数字 -1

可以选择很多种方式来实现,我们这里选择用原生的方式Vue 的方式分别实现,并比较两者的不同。

3.1 原生实现

<h2 class="counter"></h2>
<button class="increment">+1</button>
<button class="decrement">-1</button>

<script>
  // 1. 获取展示当前计数的元素和两个按钮元素
  const counterEl = document.querySelector('.counter');
  const incrementBtnEl = document.querySelector('.increment');
  const decrementBtnEl = document.querySelector('.decrement');

  // 2. 定义一个变量,用来记录当前计数
  let counter = 0;
  // 3. 为展示当前计数的元素内容赋初值
  counterEl.innerHTML = counter;

  // 4. 为按钮添加点击事件的监听
  incrementBtnEl.addEventListener('click', () => {
    counter++;
    counterEl.innerHTML = counter;
  });
  decrementBtnEl.addEventListener('click', () => {
    counter--;
    counterEl.innerHTML = counter;
  });
</script>

计数器-原生实现

3.2 Vue 实现

<div id="app"></div>

<!-- 引入 Vue -->
<script src="./js/vue.js"></script>
<script>
  Vue.createApp({
    // 模板内容有点多,写在一行不好写,可以采用 ES6 新增的模板字面量定义字符串,这样就能保留换行字符,
    // 可以跨行定义字符串了。
    // 下面模板内容的最外层包了个 div,这是 Vue2 的要求,而到了 Vue3 中,可以去掉这个 div。
    // Vue3 的 template 最外层根元素(如下面的 div)可以不加(Vue2 必须要加一层根元素进行包裹,
    // 否则会报错:Component template should contain exactly one root element. If you are
    // using v-if on multiple elements, use v-else-if to chain them instead.)。
    // 模板中可以通过“Mustache”语法(双大括号)使用下面 data 函数属性返回对象中的属性;
    // 模板中可以通过“@click”绑定点击事件,与下面 methods 对象中的属性进行绑定。
    template: `
      <div>
        <h2>{{ message }}</h2>
        <h2>{{ counter }}</h2>
        <button @click="increment">+1</button>
        <button @click="decrement">-1</button>
      </div>
    `,
    // createApp 函数需要传入的是一个对象,对象是可以有多个属性的,
    // 而 Vue 中规定了,这里还可以有 data 属性。
    // Vue3 中 data 属性对应的值需要是一个函数并返回一个对象,而不允许直接传对象,
    // 否则会报错:Uncaught TypeError: dataFn.call is not a function(Vue2 中 data 可以直接传一个对象)
    data() {
      return {
        // data 函数返回的对象中可以定义任意属性;
        // 它们都会被加入到 Vue 的响应式系统中,所以都是响应式的;
        // 它们都可以在模板中使用
        message: 'Hello World',
        counter: 0
      };
    },
    // 还可以有 methods 属性
    // methods 属性对应一个对象,里面可以定义各种各样的方法
    methods: {
      // 定义“加一”操作的方法(ES6 之前对象中定义方法的语法)
      increment: function() {
        console.log('点击了 +1');
        // methods 中可以通过 this 拿到 data 中的数据(本质上拿到的是 Proxy)
        this.counter++;
      },
      // 定义“减一”操作的方法(ES6 对象中定义方法的语法糖,即简写形式)
      decrement() {
        console.log('点击了 -1');
        this.counter--;
      }
    }
  }).mount('#app');
</script>

计数器-Vue 实现

我们还可以对上述代码做一点抽取(template 的抽取 4.1.1 节会详细讲),修改如下:

<div id="app"></div>

<template id="my-app">
  <h2>{{ message }}</h2>
  <h2>{{ counter }}</h2>
  <button @click="increment">+1</button>
  <button @click="decrement">-1</button>
</template>

<script src="./js/vue.js"></script>
<script>
  const App = {
    template: '#my-app',
    data() {
      return {
        message: 'Hello World',
        counter: 0
      };
    },
    methods: {
      increment: function() {
        console.log('点击了 +1');
        this.counter++;
      },
      decrement() {
        console.log('点击了 -1');
        this.counter--;
      }
    }
  };

  Vue.createApp(App).mount('#app');
</script>

3.3 命令式和声明式

我们会发现,原生开发和 Vue 开发的模式和特点是完全不同的,这里其实涉及到两种不同的编程范式

  • 命令式编程声明式编程
  • 命令式编程关注的是 “怎么做”(how to do声明式编程关注的是 “做什么”(what to do”怎么做“(how to do)的过程交给框架(机器)完成

在原生的实现过程中,我们是如何操作的呢?

  • 我们每完成一个操作,都需要通过 JavaScript 编写一条代码,来给浏览器一个指令
  • 这样的编写代码的过程,我们称之为命令式编程
  • 在早期的原生 JavaScriptjQuery 开发中,我们都是通过这种命令式的方式编写代码的;

Vue 的实现过程中,我们是如何操作的呢?

  • 我们会在 createApp 传入的对象中声明需要的内容:模板(template)、数据(data)、方法(methods),剩下的具体操作则由 Vue 帮助我们完成;
  • 这样编写代码的过程,我们称之为声明式编程
  • 目前 VueReactAngular 的编程模式,都是这种声明式。

3.4 MVVM 模型

  • MVCMVVM 都是一种软件的体系结构

    • MVCModel-View-Controller 的简称,是前期使用非常广泛的框架的架构模式,比如 iOS、前端;
    • MVVMModel-View-ViewModel 的简称,是目前非常流行的架构模式;
  • 通常情况下,我们也称 Vue 是一个 MVVM 的框架

    • Vue 官网其实有说明:“虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发”。

MVVM 软件架构模式

前面原生实现计数器的代码就可以按照 MVC 的软件体系结构进行划分(虽然划分得不是很清晰):

MVC 架构模式

而前面 Vue 实现计数器的代码则是按照 MVVM 的软件架构模式编写的:

MVVM 架构模式

综上,计数器的原生实现可以看成是一个 MVC 架构模式的命令式编程,计数器的 Vue 实现可以看成是一个 MVVM 架构模式的声明式编程。

4. templatedatamethods 属性详解

在使用 createApp 的时候,我们传入了一个对象,下面我们详细解析一下之前传入的属性分别代表什么含义。

4.1 template 属性

template 属性表示的是 Vue 需要帮助我们渲染的模板信息。

  • 目前我们看到它里面有很多的 HTML 标签,这些标签会替换掉我们挂载到的元素(我们这里是 idappdiv)的 innerHTML
    • 源码中对应的代码:packages/runtime-dom/src/index.ts -> createApp -> app.mount -> container.innerHTML = ''Vue 会在挂载前清空里面的内容;
  • 模板中有一些奇怪的语法,比如 {{}}@click,这些都是模板特有的语法,我们会在后面讲到;

但是这个模板的写法有点别扭,并且 IDE 很有可能没有任何提示,有碍我们编程的效率。

4.1.1 template 写法

好消息是,Vue 为我们提供了另外两种方式来编写 template

  • 方式一:使用 script 标签,同时标记它的类型为 x-template,设置 id
<script type="x-template" id="my-app">
  <div>
    <h2>{{ message }}</h2>
    <h2>{{ counter }}</h2>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
  </div>
</script>
  • 方式二:使用任意标签(通常是 template 标签,因为它不会被浏览器渲染),设置 id
    • HTML 内容模板(<template>)元素是一种用于保存 HTML 的机制,它不会在页面加载完后立即被渲染(呈现),但随后在运行时使用 JavaScript 期间可能会被实例化。1
<template id="my-app">
  <div>
    <h2>{{ message }}</h2>
    <h2>{{ counter }}</h2>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
  </div>
</template>
  • 这个时候,在给 createApp 函数传入的对象中,我们需要传入的 template# 开头
    • 如果字符串是以 # 开始,那么它将被用作 querySelector,并且使用匹配元素的 innerHTML 作为模板字符串。

方式一完整代码:

<div id="app"></div>

<script type="x-template" id="my-app">
  <div>
    <h2>{{ message }}</h2>
    <h2>{{ counter }}</h2>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
  </div>
</script>

<script src="./js/vue.js"></script>
<script>
  Vue.createApp({
    template: '#my-app',
    data() {
      return {
        message: 'Hello World',
        counter: 0
      };
    },
    methods: {
      increment: function () {
        this.counter++;
      },
      decrement() {
        this.counter--;
      }
    }
  }).mount('#app');
</script>

方式二完整代码:

<div id="app"></div>

<template id="my-app">
  <div>
    <h2>{{ message }}</h2>
    <h2>{{ counter }}</h2>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
  </div>
</template>

<script src="./js/vue.js"></script>
<script>
  Vue.createApp({
    template: '#my-app',
    data() {
      return {
        message: 'Hello World',
        counter: 0
      };
    },
    methods: {
      increment: function () {
        this.counter++;
      },
      decrement() {
        this.counter--;
      }
    }
  }).mount('#app');
</script>

开发中,推荐使用 <template> 的方式编写模板,因为有代码提示和代码高亮。

4.2 data 属性

  • data 属性需要传入一个函数,并且该函数需要返回一个对象:
    • Vue2.x 的时候,也可以传入一个对象(仅就 Vue 实例而言,如果是组件的 data,则只接受 function);
    • Vue3.x 的时候,必须传入一个函数,否则会直接在浏览器中报错;
  • data 中返回的对象会被 Vue 的响应式系统劫持,之后对该对象的访问或修改都会在劫持中被处理;
    • 所以我们在 template 中通过 {{ counter }} 访问 counter,可以从对象中获取到数据;
    • 所以我们修改 counter 的值时,template 中的 {{ counter }} 也会发生改变;
  • 这种响应式的具体原理,我们后面会有专门的篇幅来讲解。

4.3 methods 属性

  • methods 属性是一个对象,我们通常会在这个对象中定义很多的方法:

    • 这些方法可以被绑定到 template 模板中;
    • 在这些方法中,我们可以使用 this 关键字来直接访问到 data 中返回的对象的属性;
  • 对于有经验的同志,这里可以就官方文档中的以下这段描述2思考两个问题:

    1. 为什么不能使用箭头函数?(官方文档给出的解释真的理解了吗?)
    2. 不使用箭头函数的情况下,this 到底指向什么?(可以作为一道面试题)

不应该使用箭头函数来定义 method 函数

4.3.1 methods 中的方法绑定的 this

4.3.1.1 为什么不能使用箭头函数
  • 我们在 methods 中要使用 data 返回对象中的的数据:

    • 那么这个 this 就必须有值,并且应该可以通过它(this)获取到 data 返回对象中的数据
  • 如果我们使用箭头函数,这里的 this 会是什么呢?

    • this 会是 window 对象
  • 为什么是 window 呢?

    • 这就涉及到箭头函数是如何决定 this 的了:箭头函数是根据外层(函数或者全局)作用域来决定 this 的,它会继承外层函数调用的 this 绑定。
    • 按照这个机制,这里最终找到的就是最外层 script 作用域中的 this,所以就是 window 了。 为什么不能使用箭头函数.png
  • this 的到底是如何查找和绑定的呢?

4.3.1.2 此处 this 的指向
  • this 指向组件实例对象上的 proxy 对象(关于 proxy,等到后面讲响应式原理时再说);

  • 有关源码如下: methods 中的 this 绑定流程.png methods 中的 this 绑定图解.png

4.4 其它属性

当然,createApp() 函数传入的对象中还有很多其它的属性,我们会在后续进行讲解:

  • 比如 propsemitssetupcomputedwatch 等等;
  • 也包括很多的生命周期函数;

不用着急,我们会一个个学习它们的。

5. Vue 源码的下载、调试、阅读

  • 有同志可能会疑惑,前面第 2 节不是已经下载了 Vue 的源码了吗?怎么又要下载?
    • 因为前面下载的 Vue 源码是已经打包过的,所有代码都放到一个文件中了,看起来非常不方便。
  • 也有同志可能会问,第 2 节中通过 CDN 下载的 Vue 的源码中不仅有 ES5 的代码,怎么还有 ES6 的代码呢?
    • 这是因为事实上最终在项目打包时,通过 babel 工具进行设置之后,这个代码还会进行一次打包(二次打包,而且还会做压缩等其它处理),所以暂时还存在 ES6 的代码不会有问题。

对于第 2 节中通过 CDN 下载的 Vue 的源码,我们可能会阅读里面的某个函数,但不会按照这份打包后的源码一点点地去读。那我们该怎么办呢?我们会这样做:

  1. 打开 GitHub 网站,左上方搜索框里输入 vue-next(即 Vue3 版本)进行搜索;

  2. 点击搜索结果中的 vuejs/vue-next(我们可以看到,它是用 TypeScript 写的);

  3. 进入 vuejs/vue-next 仓库后,接下来有两种方式进行下载:

  • git clone 方式(推荐):点击 Code 按钮,直接点击下面 URL 链接后面的复制按钮复制 URL,然后我们以 git clone 的方式下载源码(为什么选择这种方式?因为 git 在运行它的 dev 时,会依赖 git 的一些东西,有用 git 做一些校验。如果直接选择 Download ZIP,里面没有 git(缺少一个 .git 文件夹),到时候代码可能会运行不起来):
    • 打开终端(Windows 上是 cmd 或者使用 Git Bash);
    • 通过 cd 命令进入待会下载的源码你想要存储的目录;
    • 执行 git clone 刚才复制的URL 命令,下载源码;
  • Download ZIP 方式:点击 master 下拉按钮,点击 Tags 标签,选择最新的稳定的版本进行下载(我们一般不会下载 Branches 下的 master 主分支或其它 dev 分支(最终会合并到 master 主分支中),因为它们很可能是正在进行开发中的代码,存在不稳定性;而 Tags 下的 xxx-beta.x 则是测试版,也不够稳定),我们这里就选择 v3.1.4 版本进行下载:

Vue3 源码选择版本下载

如果你选择了 Download ZIP 的方式下载了源码,又想让源码运行起来,可以这么做:

  1. 先解压下载下来的源码压缩包,用 Visual Studio Code 打开解压后的文件夹(vue-next-3.1.4);
  2. Visual Studio Code 中打开终端,输入 yarn install 命令并执行;
    • 这里需要你电脑上之前已经安装过 node 了,并且用 node 安装过 yarn 了,如果没有,需要先下载 node(建议去 node 官网下载“长期支持版”(LTS)的,因为相对于“当前发布版”(Current)的,“长期支持版”的会更稳定一点);
    • 下载安装完 node 之后,就会有 npm 这个工具了,就可以在命令行终端执行 npm install yarn -g 命令,然后你的电脑上就有 yarn 这个工具了;
    • 因为 Vue 的源码是用 yarn 来管理的(源码目录下可以看到 yarn.lock 文件),所以我们需要执行 yarn install 命令安装 Vue 源码所需的依赖。
  3. 执行 git init 命令将当前文件夹(vue-next-3.1.4)初始化为一个空的 Git 仓库;
  4. 执行 git add . 命令将当前目录下的所有文件添加到暂存区;
  5. 执行 git commit -m "fix(install): install dependences" 命令将暂存区内容提交到本地仓库中;
  6. 执行 yarn dev(本质上会去执行当前文件夹下 package.json 文件中的 scripts 中的 dev 脚本对应的命令)命令,它会把当前文件夹下的 packages 文件夹下的所有文件(都是 Vue 的源代码)进行打包,打包后的代码会放到 packages/vue/dist/vue.global.js 文件(该文件其实就是上传到 CDN 上面的那个文件)中;
  7. 有了这份打包后的源代码文件,我们就可以来到 packages/vue/examples 目录下,新建一个目录(如 coderzhj),再在该目录中新建一个 html 文件(如 demo.html),接着在该文件中引入这个打包后的源码文件后,就可以用 Vue 编写一些代码并在 Vue 的项目里跑起来了:

Vue 源码打包后使用

而如果是通过 git clone 方式下载的源码,则无需解压,用 Visual Studio Code 直接打开源码存放位置下的 vue-next 文件夹。由于 git clone 方式下载下来的是整个仓库的代码,默认是 master 主分支版本,如果想切换成其它版本(比如当前最新的稳定版本 v3.1.4),可以先在终端执行 git tag 命令查看都打包过哪些 tag,再通过 git checkout v3.1.4 切换到 v3.1.4 版本(不切换的话,默认使用 master 也没有太大问题),然后执行 yarn install 后直接执行 yarn dev(或 npm dev)即可。

那跑起来后有什么用呢?用处就是我们可以在 script 标签中打上 debugger 进行调试啦:

在 Vue 源码项目中进行调试 1

比如,我们想看下 createApp 究竟做了些什么,就可以 F10Vue.createApp 这行,再 F11 跳转进 createApp 函数:

在 Vue 源码项目中进行调试 2

但是,我们会发现这样做是在 vue.global.js 这个大文件中进行的调试,这意味着所有源代码都是放到了一起的,看起来非常不方便。那有没有办法让调试的时候跳转到某个具体的源代码文件中呢?有,我们可以修改项目目录下 package.json 文件中的 scripts 中的 dev 脚本对应的命令,在该命令最后添加上 --sourcemapwebpack 相关的内容):

设置代码映射

然后重新执行 yarn dev 把项目重新打包后跑起来(这时 dist 目录下会多出来一个 vue.global.js.map 文件,这个就是 sourcemap 文件,用来方便我们调试),这样一来,调试过程中的每一行代码都可以映射到它所在的具体文件中了,那么我们就能通过映射的文件和项目中的 packages 文件夹下的文件对应起来了:

代码映射效果

在 Vue 项目中的对应文件中查看

写在最后

文中如有错误,欢迎大家在评论区指正。

Footnotes

  1. The <template> HTML element is a mechanism for holding HTML that is not to be rendered immediately when a page is loaded but may be instantiated subsequently during runtime using JavaScript. -- MDN

  2. 详见 v3.cn.vuejs.org/api/options…