当前位置:首页 » 《随便一记》 » 正文

1. Vue 3.0介绍

19 人参与  2022年11月28日 15:53  分类 : 《随便一记》  评论

点击全文阅读


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中响应式系统的核心是definePropertyVue 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编译单文件组件,并把编译的结果返回给浏览器。


点击全文阅读


本文链接:http://zhangshiyu.com/post/49193.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1