Vue3.0 介绍

源码组织方式
  • 提升代码可维护性,源码采用 TypeScript 重写

  • 使用 Monorepo 管理项目结构,将独立模块提取到不同的包中,每个模块划分明确,模块依赖关系也很明确,并且每个功能模块都可以单独测试、发布并使用

image-20210412192848527.png

compiler开头的包都是和编译相关的代码,compiler-core是和平台无关的编译器,compiler-dom是浏览器平台下的编译器,依赖compiler-corecompiler-sfc(single file component)单文件组件,用于编译单文件组件,依赖compiler-corecompiler-domcompiler-ssr是和服务端渲染相关的编译器,依赖compiler-dom

reactivity是数据响应式系统,可单独使用

runtime开发的包都是运行时代码,runtime-core是和平台无关的运行时,runtime-dom是针对浏览器的运行时,处理原生 DOM API、事件等;runtime-test是为测试而编写的轻量的运行时,渲染出的 DOM 树是一个 js 对象,所以这个运行时可以运行在所有的 js 环境里,用它来测试渲染是否正确,还可以用于序列化 DOM、触发 DOM 事件以及记录某次更新中的 DOM 操作

server-renderer是服务端渲染

shared是 vue 内部使用的一些公共 API

size-check是私有包,不会发布到 npm,用于在 tree-shaking 后检查包的大小

template-explorer是在浏览器里运行的实时编译组件,会输出 render 函数

vue构建完整版 vue,依赖于compilerruntime

Vue.js3.0 不同构建版本

构建不同版本,用于不同的场合,和 vue2.x 不同的是,不再构建 umd 模块方式,umd 模块方式会让代码更加冗余

  • packages/vue 存在所有构建版本

    image-20210412193022815.png
  • 说明

    官网不同构建版本的解释

    版本 名称 说明
    cjs(commonJS 模块化方式, vue.cjs.js 开发版,代码未被压缩
    完整版 vue,包含运行时和编译器) vue.cjs.prod.js 生产版本,代码被压缩
    global(全局,这 4 个文件都可以在浏览器中 vue.global.js 完整版,开发版
    通过 script 的方式引入,增加全局 vue 对象) vue.global.prod.js 完整版,生产版
    vue.runtime.global.js 运行时版本,开发版
    vue.runtime.global.props.js 运行时版本,生产版
    browser(esModule 模块化方式,在浏览器中 vue.esm-browser.js 完整版,开发版
    通过 type="module"的方式来导入) esm-browser.prod.js 完整版,生产版
    vue.runtime.esm-browser.js 运行时版本,开发版
    vue.runtime.esm-browser.prod.js 运行时版本,生产版
    bundler(需要配合打包工具使用,使用 es Module vue.esm-bundler.js 完整版,还导入 runtime-compiler
    方式,内部通过 import 导入 runtime-core) vue.runtime.esm-bundler.js 运行时,通过脚手架创建项目默认使用此版本
Composition API

设计动机

  • Options API

    包含一个描述对象组件选项(data、methods、props 等)的对象

    Options API 开发负责组件,同一个功能逻辑的代码被拆分到不同选项中

    export default {
      data() {
        return {
          position: {
            x: 0,
            y: 0,
          },
        }
      },
      created() {
        window.addEventListener(' mousemove ', this.handle)
      },
      destroyed() {
        window.removeEventListener('mousemove ', this.handle)
      },
      methods: {
        handle(e) {
          this.position.x = e.pagexthis.position.y = e.pageY
        },
      },
    }
    
  • Composition API

    Vue.js3.0 中新增的一组 API

    一组基于函数的 API

    可以更灵活的组织组件的逻辑

    解决超大组件时,使用 Options API 不好拆分和重用问题

    // CompositionAPI
    import { reactive, onMounted, onUnmounted } from 'vue'
    function userMousePosition() {
      const position = reactive({
        x: 0,
        y: 0,
      })
      const update = (e) => {
        position.x = e.pageX
        position.y = e.pageY
      }
      onMounted(() => {
        window.addEventListener('mousemove', update)
      })
      onUnmounted(() => {
        window.removeEventListener('mousemove', update)
      })
      return position
    }
    
    export default {
      setup() {
        const position = useMousePosition()
        return {
          position,
        }
      },
    }
    
    image-20210412200549917.png

同一色块代表同一功能,Options API 中相同的代码被拆分在不同位置,不方便提取重用代码

Composition API 同一功能代码不需要拆分,有利于代码重用和维护

  • Composition Api vs Options Api

    • 在逻辑组织和逻辑复用方面,Composition API是优于Options API
    • 因为Composition API几乎是函数,会有更好的类型推断。
    • Composition APItree-shaking 友好,代码也更容易压缩
    • Composition API中见不到this的使用,减少了this指向不明的情况
    • 如果是小型组件,可以继续使用Options API,也是十分友好的
性能提升
  • 响应式系统升级

    Vue.js2.x 中响应式系统核心是 defineProperty,初始化时遍历所有 data 中的成员,通过 defineProperty 将对象属性转换为 getter 和 setter,如何 data 中的对象又是对象的话,需要递归处理每一个子对象属性

    Vue.js3.0 中使用 Proxy 对象重写响应式系统,可以拦截属性的访问、赋值、删除等操作

    Proxy 好处:

    1. 可以监听动态新增属性,vue2.x 需要使用$set
    2. 可以监听删除的属性,vue2.x 监听不到
    3. 可以监听数组的索引和 length 属性,vue2.x 监听不到
  • 编译优化

    对编译器进行优化,重写虚拟 DOM,首次渲染和 update 性能有了大幅度提升,这也是Vue3性能能够得到提升的重要原因之一

    <template>
      <div id="app">
        <div>
          static root
          <div>static node</div>
        </div>
        <div>static node</div>
        <div>static node</div>
        <div>{{ count }}</div>
        <button @click="handler">button</button>
      </div>
    </template>
    

    Vue.js2.x 在构建过程中需要先编译为 render 函数,在编译时通过标记静态根节点,优化 diff 过程(但是依然需要执行 diff 操作),当组件发生变化时,会通知 watcher,触发 watcher 的 update 方法,最终执行虚拟 DOM 的 patch 操作,遍历所有虚拟节点找到差异,然后更新到真实 DOM 上;diff 过程中会比较整个虚拟 DOM,先对比新旧的 div,以及它的属性,再去对比内部子节点;

    Vue.js2.x 中渲染的最小单位是组件

    Vue.js3.0 中标记和提升所有静态根节点,diff 时只需要对比动态节点内容

    • Fragments 片段,模板中不需要在创建唯一的根节点,需要升级 vetur 插件,查看Vue 3 Template Explorer

      image-20210413082819879.png

首先使用_createBlock给根 div 创建 block,是树结构,然后通过_createVNode创建子节点,相当于h函数,当删除根节点时,会创建_Fragment片段

image-20210413083059655.png
  • 静态提升

    打开hoistStatic静态提升选项,可以看到_createBlock中的静态节点都被提升到 render 函数外边,这些节点只有初始化时被创建一次,再次调用 render 时不会在被创建

    image-20210413083144478.png
  • Patch flag
image-20210413083558220.png

可以看到在动态节点<div>{{ count }}</div>通过_createVNode渲染后,最终会有数字1,这就是 Patch flag。作为一个标记,将来在执行 diff 时会检查整个block中带 Patch flag 标记的节点,如果 Patch flag 值为1,代表文本内容时动态绑定,所以只会比较文本内容是否发生变化

image-20210413084010181.png

此时在给当前 div 绑定一个 id 属性,可以看到 Patch flag 变为9,代表当前节点的文本和 PROPS 是动态内容,并且记录动态绑定的 PROPS 是 id,将来 diff 时只会检查此节点的文本和 id 属性是否发生变化,从而提升 diff 性能

  • 缓存事件处理函数
image-20210413084318107.png

开启缓存后,首次渲染时会生成新的函数,并将函数缓存到_cache对象中,将来再次调用 render 时,会从缓存中获取

  • 源码体积优化

    Vue.js3.0 移除一些不常用 API,如:inline-template、filter 等

    Tree-shaking 支持更好,因为 Tree-shaking 依赖 ES Module,也就是 ES6 的模块化语法结构importexport,通过编译阶段的静态分析,找到没有引入的模块,在打包的时候直接过滤掉,从而减少打包体积。Vue.js3.x 的内置组件 keepAlive、Trasition 和一些内置指令都是按需引入,并且 Vue.js3.x 中的很多 API 都是支持 Tree-shaking,没有使用是不会进行打包的

Vite

学习 Vite 前,先需要了解 ES Module

  • 除 IE 外,现代浏览器都支持 ES Moduie

  • 加载模块通过在 script 标签中 type="module"即可

    <script type="module" src="..."></script>
    
  • 支持模块的 script 默认延迟加载

    类似于 script 标签设置 defer

    在文档解析完成后,触发DOMContentLoaded事件前执行

案例项目地址

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">Hello world!</div>
    <script>
      window.addEventListener('DOMContentLoaded', () => {
        console.log('DOMContentLoaded')
      })
    </script>
    <script type="module" src="./modules/index.js"></script>
  </body>
</html>
// modules/index.js
import { forEach } from './utils.js'

const app = document.querySelector('#app')
console.log(app.innerHTML)

const arr = [1, 2, 3]
forEach(arr, (item) => {
  console.log(item)
})

type="module"方式引入时需要在服务器中运行项目,在 vsCode 中安装插件live-server,右键启动项目

image-20210413103509503.png

打开浏览器控制台,可以看到输出结果如下所示,可以看到index.js模块在文档解析完成后,触发 DOMContentLoaded 事件前执行

image-20210413085620461.png

Vite vs Vue-Cli

  • Vite 在开发模式下不需要打包可以直接运行,因为 vite 在开发模式下使用浏览器支持的 es Module 加载模块,通过<script type="module"></script>的方式加载代码,提升开发效率;vite 会开启测试服务器,拦截浏览器发送请求,对浏览器不识别的模块进行处理,比如当 import 单文件组件时,会先进行编译,把编译的结果发送给浏览器
  • Vue-Cli 开发模式下必须对项目打包才可以运行
  • Vite 在生成环境下使用 Rollup 打包,基于 ES Module 的方式打包,不再需要使用 babel 把 import 转换为 require,因此打包体积会小于 webpack 体积
  • Vue-Cli 使用 Webpack 打包

Vite 特点

  • 快速冷启动(不需要打包)
  • 按需编译(代码加载时才会进行编译)
  • 模块热更新

使用 Vite 创建基于 vue3 项目

npm install create-vite-app -g
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev

基于模板创建项目

npm init vite-app --template react
npm init vite-app --template preact

案例项目地址

通过 create-vite-app 创建完项目之后,App.vue 会有 eslint 语法错误,原因是 Vetur 插件还没有更新

image-20210413085824316.png

解决:文件 --> 首选项 --> 设置 --> 搜索 eslint-plugin-vue --> Vetur › Validation: Template 取消勾选

image-20210413095847513.png

通过 npm run dev 启动项目

开发环境下,vite 开启 web 服务器后,会劫持.vue 结尾的文件,将.vue 文件转换为 js 文件,并将响应中的 content-type 设置为 application/javascript,告诉浏览器是 js 脚本

image-20210413113538441.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,548评论 0 11
  • 彩排完,天已黑
    刘凯书法阅读 4,182评论 1 3
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 124,045评论 2 7