1. Vue 3.0介绍

Vue3.0介绍

1.Vue.js 3.0 源码组织方式

Vue2.x与Vue3.0的区别

  • 源码组织方式的变化

    • Vue3.0的源码全部采用TypeScript重写
    • 使用Monorepo方式来组织项目结构,把独立的功能模块都提取到不同的包中。

    packages下都是独立发行的包,可以独立使用。

  • Composition API(组合API)

    Vue 3.0代码虽然重写,但是90%以上的API兼容2.x,并且增加了Composition API(组合API),是用来解决Vue 2.x在开发大型项目时遇到超大组件,使用options API不好拆分和重用的问题。

  • 性能提升

    Vue 3.0使用Proxy重写了响应式代码,并对编译器做了优化,重写了虚拟DOM,从而让渲染和update的性能都有了大幅度的提升,另外服务端渲染SSR的性能也提升了2-3倍。

  • Vite

    官方提供了一个开发工具Vite,使用Vite在开发和测试阶段,不用打包项目,可以直接去运行项目,提升了开发的效率。

2.不同的构建版本

3.Composition API 设计动机

  • RFC (Request For Comments)

    https://github.com/vuejs/rfcs

  • Composition API RFC

    https://composition-api.vuejs.org

Composition API 的设计动机

Options API:使用包含组件描述选项的对象来创建组件的方式。例如选项:datamethodscreated等等组成对象,来组成组件。

案例:可以看到要实现一个功能,需要在不同选项中添加。如果此时需要在添加一个功能,就需要在多个选项中添加代码。并且难以提取组件中重复的代码。

  • Options API(Vue 2.x)

    • 包含一个描述组件选项(data、methods、props等)的对象
    • Options API开发复杂组件,同一个功能逻辑的代码被拆分到不同选项
    • Options API难以提取组件中可重用的逻辑,虽然有mixin,但容易命名冲突,数据来源不清晰。
  • Composition API(Vue 3.0)

    • Vue 3.0新增的一组API
    • 一组基于函数的API
    • 可以更灵活的组织组件的逻辑

    Compisition API案例,可以看到将功能封装到一个函数内部,如果需要再增加一个功能,只需要再封装一个函数,然后在setup函数中调用函数。

官方提供的案例图,Options API中可以看到相同色块代表同一个功能,分布在不同的位置,而Composition API则是一个功能一个块。

4.性能提升

  • 响应式系统升级

    首先来看一下响应式系统升级。我们都知道Vue2的时候,数据响应式的原理使用的是defineProperty,在初始化的时候会遍历data中的所有成员。通过defineProperty,把对象的属性转换成gettersetter。如果data中的属性又是对象的话,需要递归处理每一个子对象的属性。注意这些都是在初始化的时候进行的。也就是说如果你没有使用这个属性的时候,你也把它进行了响应式的处理。

    而Vue3中采用的是ES6以后新增的proxy对象。proxy对象的性能本身就比defineProperty要好。另外,代理对象可以拦截属性的访问、复制、删除等操作。不需要初始化的时候遍历所有的属性。另外,如果有多层属性嵌套的话,只有访问某个属性的时候,才会递归处理下一级的属性,使用proxy对象默认就可以监听到动态添加的属性。而Vue2里边想要动态添加一个显示的属性需要调用this.$set的方法来处理。而且Vue2中还监听不到属性的删除,对数组的索引和length属性的修改也监听不到。Vue3中使用代理对象可以监听属性的删除以及数组的索引和length属性的修改操作。所以Vue3中使用proxy对象提升了响应式系统的性能和功能。

    • Vue 2.x中响应式系统的核心是defineProperty
    • Vue 3.0中使用Proxy对象重写响应式系统
      • 可以监听动态新增的属性
      • 可以监听删除的属性
      • 可以监听数组的索引和length属性
  • 编译优化

    Vue3中通过优化编译的过程和重写虚拟DOM,让首次渲染和更新的性能有了大幅度的提升。我们知道Vue2的时候,模板首先需要编译成render函数,这个过程一般是在构建的过程中完成的。在编译的时候会编译静态根节点静态节点。静态根节点要求节点中必须有一个静态子节点,当组件的状态发生变化后,会通知watch触发watcherupdate。最终去执行虚拟DOMpatch操作遍历所有的虚拟节点,找到差异,然后更新到真实DOM上。
    Diff的过程中会去比较整个虚拟DOM,先对比新旧的div以及它的属性,然后再对比它内部的子节点。Vue2中渲染的最小单位是组件。Vue2中diff的过程会跳过静态根节点,因为静态根节点的内容不会发生变化,也就是Vue2中通过标记静态根节点优化了diff的过程。但是在Vue2的时候,静态节点还需要再进行diff,这个过程没有被优化

    Vue3中为了提高性能,在编译的时候会标记和提升所有的静态节点,然后diff的时候只需要对比动态节点的内容。另外在Vue3中新引入了一个Fragments,也就是片段的特性,模板中不需要再创建一个唯一的根节点模板,里边可以直接放文本内容或者很多同级的标签。

    在Vs code中需要升级你的Vetur插件,否则模板中如果没有唯一的根据点VS Code依然会提示有错误。

    左边是我们刚刚看到的组件模板中的内容,右边是我们编译之后的render函数,但是这个编译的结果跟Vue2会有很大的区别,首先这里调用_createBlock给我们的根div创建了一个block,它是一个树的结构,然后通过createVNode的去创建了我们的子节点,那这里的createVNode的其实就是类似于我们之前的h函数。

    那我们来删除这里面的根节点,来看一下它的变化。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DRUSTVaP-1669553000301)(C:/Users/5coder/AppData/Roaming/Typora/typora-user-images/image-20221126161031253.png)]当我们删除根节点之后,这里会创建一个fragment,也就是我们之前说的片段。其实从这里还可以看到,它内部还是维护了一个树形的结构,那么最外层是fragment,里边是我们的这些VNode的。
    Vue 2.x中通过标记静态根节点,优化diff的过程

    • Vue 3.0中标记和提升所有的静态根节点,diff的时候只需要对比动态节点内容
      • Fragments(升级vetur插件)
      • 静态提升
      • Patch flag
      • 缓存事件处理函数
  • 源码体积的优化

    • Vue 3.0中移除了一些不常用的API(如inline-template、filter等)
    • Tree-shaking

5.Vite

ES Module

  • 现代浏览器都支持ES Module(IE不支持)
  • 通过下面的方式加载模块
  • 支持模块的script默认延迟加载(相当于省略了defer属性)
    • 类似于script标签设置defer
    • 在文档解析完成后,触发DOMContentLoaded事件前执行(加载模块并执行,是在DOM创建之后,并且在DOMContentLoaded执行之前执行的)

浏览器使用ES Module案例

项目结构:

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <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)
})

modules/utils.js

export const forEach = (array, fn) => {
  let i
  for (i = 0; i < array.length; i++) {
    fn(array[i])
  }
}

export const some = (array, fn) => {
  let result = true
  for (const value of array) {
    result = result || fn(value)
    if (result) {
      break
    }
  }
  return result
}

打开index.html我们可以发现,设置了type="module"后,script默认延迟加。并且执行结果为:先加载模块,后执行DomContentLoaded事件。

Vite与Vue-cli区别

Vite的快就是使用浏览器支持的ES module的方式,避免开发环境下打包,从而提升开发速度。下面看一下ViteVue Cli的区别,最主要的区别是Vite在开发环境下不需要打包,因为在开发模式下,Vite使用浏览器原生支持的ES module加载模块,也就是通过import来导入模块,支持ES module的现代浏览器通过script type="module"的方式加载模块代码。

因为Vite不需要打包项目,因此Vite在开发模式下打开页面是秒开的,而Vue Cli在开发环境下会先打包整个项目,如果项目比较大,速度会特别慢。

  • Vite在开发模式下不需要打包可以直接运行
  • Vue-cli开发模式下必须对项目打包才可以运行
  • Vite在生产环境下使用Rollup打包基于ES Module的方式打包
  • Vue-cli使用webpack打包

Vite特点

Vite会开启一个测试的服务器,它会拦截浏览器发送的请求,浏览器会向服务器发送请求获取相应的模块,那为此会对浏览器不识别的模块进行处理。比如当import单文件组件的时候,也就是后缀名为.vue的文件时,会在服务器上对.vue文件进行编译,把编译的结果返回给浏览器。稍后我们会演示这个过程。使用这种方式让Vite有以下的优点:

  • 快速冷启动

    因为不需要打包,所以可以快速冷启动。

  • 按需编译

    代码是按需编译的,因此只有当代码在当前需要加载的时候才会编译。你不需要在开启开发服务器的时候等待整个项目被打包,那当项目比较大的时候,这个时候就会更明显。

  • 模块热更新

    Vite支持模块热更新,并且模块热更新的性能与模块总数无关。无论你有多少模块,HMR的速度始终比较快。

另外,Vite在生产环境下使用Rollup打包,Rollup基于浏览器原生的ES Module进行打包,它不需要再使用Babel,再把import转换成require以及一些相应的辅助函数,因此打包的体积会比webpack打包的体积更小。现在浏览器都已经支持ES Module的方式加载模块。

Vite创建项目

Vite有两种创建项目的方式,一种是创建基于Vue3的项目,可以直接在终端输入npm init vite-app <项目名称>来创建项目,然后再切换到项目目录cd <项目名称>,输入npm i安装依赖。最后通过npm run dev在开发环境下运行项目。

还有一种方式是基于模板创建项目,Vite基于模板的方式可以让它支持其他的框架,在创建项目的时候,后面跟上要使用的框架,比如是--template react

这里我们演示第一种方式。

使用第一种方式创建后的项目目录结构如下:

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <link rel="icon" href="/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vite App</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.js"></script>
</body>
</html>

main.js

import { createApp } from 'vue'
import App from './App.vue'  // 将单文件组件当成模块来加载
import './index.css'

createApp(App).mount('#app')

APP.vue

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <HelloWorld msg="Hello Vue 3.0 + Vite" />
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

使用npm run dev运行项目,可以开发服务器开启的速度很快,因为他没有打包的过程。

网页源代码:

打开开发人员工具,找到NetWork

这里有很多请求,找一下.vue结尾的这块有一个app.vue。Vite开启的这个web服务器,它会劫持.vue的请求,它首先会把.vue文件解析成JS文件,并且把响应头中的contentType设置为application/javascript,目的是告诉浏览器我现在给你发送的是一个javascript脚本

那下面我们再来看response,这是服务器解析的js模块,这里又导入了helloWorld.vue这个单文件组件。

注意import { render as __render } from "/src/App.vue?type=template"。这里又通过import导入了App.vue模块,后面加了一个参数:type="template"。这里导入这个模块的render函数,注意,我们的App.vue是单文件组件,在编写的时候根本没有写render函数。现在之所以能导出这个render函数,是因为服务器对它做了特殊的处理,我们一会儿来解释。那这里只要加载模块就会向服务器发送请求,请求这个模块。

再来往下看,这里又请求了HelloWorld.vue这个单文件组件,它的处理方式跟刚刚的App.vue是一样的。再往下看的话,这里又请求了App.vue?type=template。这次请求到服务器之后,服务器会把这个App.vue这个单文件组件通过vue中的模块compile-sfc给它编译成render函数。

可以看到response中的内容,这里首先把静态节点提升,然后下面是render函数。这就是Vite的工作原理:它使用浏览器支持的ES Module的方式来加载模块。在开发环境下,它不会打包项目,把所有的模块的请求都交给服务器来处理,在服务器去处理浏览器不能识别的模块。如果是单文件组件,会调用compile-sfc编译单文件组件,并把编译的结果返回给浏览器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

5coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值