Vue 3 学习 一、Vue 3.0 介绍+Vue2.x 对比+Vite介绍

Vue.js 3.0 源码组织方式

  • 为了提升代码的可维护性,Vue 3.0 的源码全部使用 TypeScript 重写
    • 大型项目的开发,都推荐使用类型化的语言
    • 在编码的过程中,帮助检查类型问题
  • 使用 monorepo 方式组织项目结构,把独立的功能模块都提取到不同的 package 中管理。
    • 这样每个功能模块,划分都很明确。
    • 模块之间的依赖关系也很明确。
    • 并且每个功能模块都可以单独测试、发布、使用。

packages 目录结构

packages 目录下都是独立发布的包(package),可以独立使用。

在这里插入图片描述

  • compiler-* 和编译相关的代码
    • compiler-core 和平台无关的编译器
    • compiler-dom 浏览器平台下的编译器,依赖于 compiler-core
    • compiler-sfc 用来编译SFC(单文件组件),依赖于 compiler-corecompiler-dom
    • compiler-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 函数
    • 这个包的 README 中提供在线访问的地址
  • vue 用于构建完整版的 Vue,依赖 compiler-*runtime-*

不同的构建版本

Vue 3 在构建的时候和 Vue 2.x 类似,都构建了不同的版本。

和 Vue 2.x 不同的是,Vue 3 中不再构建 umd 的模块化方式。

因为 umd 模块化的方式会让代码有更多的冗余,它要支持多种模块化的方式。

Vue 3 的构建版本中,把 cjs、esm 和 自执行函数的方式分别打包到了不同的文件中。

在 packages/vue/dist 目录中存放了 Vue 3 的所有构建版本,总共分为四类:

在这里插入图片描述

  • cjs - CommonJS 的模块化方式,这里的两个文件都是完整版的 Vue,包含运行时和编译器

    • vue.cjs.js - 开发版本(代码没有被压缩)
    • vue.cjs.prod.js - 生产版本(代码被压缩)
  • global - 全局,这四个文件都可以在浏览器中直接通过 script 标签导入,导入js后,会增加一个全局的 Vue 对象

    • vue.global.js - 完整版+开发版本
    • vue.global.prod.js - 完整版+生产版本
    • vue.runtime.global.js - 运行时版+开发版
    • vue.runtime.global.prod.js - 运行时版+生产版本
  • browser - ESM(浏览器原生模块化方式),在浏览器中可以直接通过 script 标签 type=“module” 的方式导入

    • vue.esm-browser.js - 完整版+开发版本
    • vue.esm-browser.prod.js - 完整版+生产版本
    • vue.runtime.esm-browser.js - 运行时版+开发版
    • vue.runtime.esm-browser.prod.js - 运行时版+生产版本
  • bundler - 这两个文件没有打包所有的代码,它们需要配合打包工具来使用。这两个文件都使用 ESM 模块化的方式,内部通过 import 导入了 runtime-core

    • vue.esm-bundler.js - 完整版,内部还导入了编译器
    • vue.runtime.esm-bundler.js - 使用脚手架创建的项目中,默认导入的这个文件。
      • 这个文件中只导入了运行时,它是 Vue 的最小版本。
      • 在项目开发完毕后,重新打包的时候,只会打包使用到的代码,它可以让 Vue 的体积更小。

Composition API

Vue 3 中 90% 以上的 API 依然兼容 2.x,并且根据社区的反馈,增加了 Composition API(组合 API)。

学习 Composition API 的最好的方式就是查看官方的 RFC(Request For Comments)

Vue 2 涉及到 Vue 3 的大的变动都是通过 RFC 的机制进行确认。

首先官方给出些提案,然后收集社区的反馈并讨论,最终确认。

Composition API RFC 文档

设计动机

它是用来解决 Vue 2.x 开发大型项目时,遇到超大组件,使用 Options API 不好拆分和重用的问题。

Vue 2.x 在开发中小型项目的时候已经很好用,但是在开发需要长期迭代和维护的大型项目时也会有些限制。

在大型项目中可能会有一些功能比较复杂的组件,在看他人开发的组件的时候可能会很难看懂。

原因是 Vue 2.x 版本的组件,开发采用的 Options API

Options API 指的是使用一个包含描述组件选项(data、methods、props等)的对象,来创建组件的方式。

Options API 开发的复杂组件,同一个功能逻辑可能被拆分到不同选项中(data 、methods、props、computed 等)。

为了查看某个功能,可能需要不停的上下拖动滚动条,来找到同一功能对应的代码,所以可能会看不懂。

Options API 示例:

// 鼠标移动时在页面中展示鼠标的位置
// 该功能被拆分到 data created destroyed methods 4个选项
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.pageX
      this.position.y = e.pageY
    }
  }
}

另外,使用 Options API 也难以提取组件中可重用的逻辑。

虽然 Vue 2.x 中有 mixin 混入的机制,可以把组件中重复的代码提取并重用。

但是 mixin 使用的过程也有问题,比如命名冲突,或者数据来源不清晰。

Composition API

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

使用 Composition API 重新演示上面示例的功能

import { reactive, onMounted, onUnmounted } from 'vue'
// 将获取数据位置的逻辑封装到一个函数
function useMousePosition() {
  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
    }
  }
}

Composition API 相对于 Options API 的好处是:

  • 查看某个逻辑的时候,只需要关注具体的函数即可。

  • 当前的逻辑代码都封装在内部,不像 Options API 分散在不同的位置。

官方对比图

同一色块代表同一功能。

Options API 中相同功能的代码被拆分到不同的位置。

查看复杂功能代码时,需要不停上下拖动滚动条找到需要的代码,而且不方便提取重用代码。

Composition API 中同一功能的代码不需要拆分,有利于代码的提取和重用。

vs

总结

Composition API 提供了一种基于函数的 API,可以更灵活的组织组件的逻辑。

使用 Composition API 可以更合理的组织组件内部的代码结构,还可以把一些逻辑功能,从组件中提取出来,方便其他组件重用。

性能提升

  • 响应式系统升级
    • Vue 3 使用 Proxy 重写了响应式系统
  • 编译优化
    • 通过优化编译的过程和重写虚拟DOM,让首次渲染和更新的性能有了大幅的提升
  • 源码体积的优化
    • 通过优化源码的体积 和 更好的 tree-shaking 的支持,减少打包的体积
  • 另外,官方介绍,服务端渲染的性能提升了 2 - 3 倍。

响应式系统升级

  • Vue 2.x 中响应式系统的核心是 defineProperty
    • 在初始化的时候会遍历 data 中的所有成员,通过 defineProperty 把对象的属性转换成 getter/setter。
    • 如果对象中的属性也是对象,要递归处理每一个子对象的属性。
    • 这些都是在初始化的时候处理的,也就表示如果属性并没有被使用,也会将它转化成响应式的。
  • Vue 3.0 中使用的是 ES6 新增的 Proxy 对象重写响应式系统
    • Proxy 的性能本身就比 defineProperty 好
    • 另外 Proxy 对象可以拦截属性的访问、赋值、删除等操作,不需要初始化的时候遍历所有的属性
    • 如果有多层属性嵌套的情况,只有访问某个属性的时候,才会递归处理下一级的属性。
    • 使用 Proxy 对象,可以监听到动态添加的属性,而 Vue 2.x 中想要动态添加一个响应式属性,需要调用 Vue.set() 方法。
    • Vue 2.x 中还监听不到属性的删除,以及数组的索引 和 length 属性的修改。而这些 Proxy 都可以做到 。
    • 所以 Vue 3.0 使用 Proxy 对象,提升了响应式系统的性能和功能。

编译优化

回顾 Vue 2.x 中编译的过程

回顾 Vue 2.x 中编译的过程:

<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>
  • 模板首先需要编译成 render 函数,这个过程一般是在构建的过程中完成的。
  • 在编译的时候会编译静态根节点和静态节点。
  • 静态根节点要求节点中必须有一个静态子节点,例如:
<div>static root
  <div>static node</div>
</div>
  • 当组件的状态发生变化后,会通知 watcher,触发 update
  • 最终去执行虚拟 DOM 的 patch 操作,遍历所有的虚拟节点,找到差异,然后更新到真实DOM上。
  • Diff 的过程中,会去比较整个虚拟DOM,先对比新旧的div,以及它的属性。然后再对比内部的子节点。
  • Vue 2.x 中渲染的最小单位是组件。

Vue 3 通过优化编译的过程和重写虚拟DOM,让首次渲染和更新的性能有了大幅的提升。

Diff 过程优化

Vue 2.x 中通过标记静态根节点,优化 Diff 的过程:

  • Vue 2.x 中 Diff 的过程会跳过静态根节点,因为静态根节点的内容不会发生变化。
  • 但是静态节点还需要再进行 Diff。

通过 Vue 3 Template Explorer 编译示例代码,查看Vue 3 中在编译时候的优化:

  • Vue 3 中新引入了一个特性: Fragments (片段)
    • 模板中不需要再创建一个唯一的根节点(默认会包含在一个 Fragment 中)。
    • 模板中可以直接放文本内容,或者很多同级的标签
    • vscode 中需要升级 vetur 插件,否则模板中没有唯一的根节点,会提示错误

尝试删除最外层的 div:

在这里插入图片描述

在这里插入图片描述

  • 静态提升:标记和提升所有的静态节点
    • 静态节点都会被提升到 render 的外层
    • 这些被提升的静态节点只有在初始化的时候被创建一次,当再次调用render的时候,不需要再次创建这些静态节点,可以直接重用上一次创建的静态节点对应的VNode

勾选 Options 中的 hoistStatic 查看效果:

在这里插入图片描述

  • Patch flag:标记动态节点,Diff 的时候只需要对比动态节点的内容。
    • Patch flag 是一个标记,将来在执行 Diff 的时候,会检查整个 block 中带 Patch flag 标记的节点
    • 1 代表注释中的 TEXT (文本内容)是动态绑定的,在 Diff 时只会比较文本内容是否发生变化
    • 9 代表注释中的 TEXT 和 PROPS 时动态内容,后面还记录了动态绑定的属性名称是 id。
      • 将来 Diff 的时候只会检查这个节点的文本和动态属性id是否发生变化
    • 这样大大提升了虚拟DOM Diff 的性能

在这里插入图片描述

在这里插入图片描述

Diff 的过程是最耗时的,在 Vue 2.x 中重新渲染的时候,需要重新创建新旧VNode,Diff 的时候会跳过静态根节点,对比剩下的每一个新旧 VNode,哪怕这个节点什么都没做。

Vue 3 中通过标记和提升静态节点,以及Patch flag 标记动态节点,大大提升了 Diff 的性能

  • 缓存事件处理函数

explorer 的 Options 中有一个 cacheHandlers 选项,可以查看缓存事件处理函数的效果。

Vue 3 中将绑定事件的处理函数存储到缓存中,调用时先判断缓存中是否存在,如果存在则调用,不存在则重新获取并存储到缓存中。

查看下面 button 编译结果:

<button @click="clickHandle">按钮</button>

在这里插入图片描述

在这里插入图片描述

优化打包体积

  • Vue 3 中移除了一些不常用的 API
    • 例如:inline-template、filter(通过computed或methods代替filter) 等
  • Tree-shaking
    • 依赖 ES6 的 ESM,通过编译阶段的静态分析,找到没有引入(import)的模块,在打包的时候直接过滤掉。
    • 内置的一些组件(比如 transition、keep-alive)和一些内置的指令(如 v-model)都是按需引入的。
    • Vue 3 中的 API 也支持 Tree-shaking,所以这些组件、指令 和 API 如果没有使用,是不会打包的。
    • 默认 Vue 的核心模块都会被打包。

查看下面内容的编译结果(手动删除 transitionv-model="value"):

<div>
  <transition></transition>
	<input v-model="value" />
</div>
  • transition 按需引入了 import { Transition as _Transition } from 'vue'
  • v-model 按需引入了 import { vModelText as _vModelText, withDirectives as _withDirectives } from 'vue'

Vite

Vue 3 的作者开发了一个自己的构建工具 Vite

这个单词来源于法语,的意思,意味着 Vite 比过去基于 webpack 的 Vue CLI 更快

使用 Vite 在开发阶段,测试项目的时候,不需要打包,可以直接运行项目,提升了开发的效率。

回顾浏览器中使用 ES Module 的方式

  • IE 之外的现代浏览器都支持 ES Module 的语法(import/export)
  • 在网页中可以直接使用 <script> 标签导入模块
    • <script type="module" src="..."></script>
  • 标记为 modulescript 标签默认是延迟加载的
    • 类似于 script 标签设置 defer
    • 在文档解析完成(DOM树生成)后,触发 DOMContentLoaded 事件前执行
<script>
  window.addEventListener('DOMContentLoaded', () => {
    console.log('DOMContentLoaded')
  })
</script>
<script type="module" src="./modules/index.js"></script>

<div id="app">Hello world</div>
// ./modules/utils.js
export const forEach = (array, fn) => {
  let i
  for (i = 0; i < array.length; i++) {
    fn(array[i])
  }
}

// ./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)
})

// 启动web服务查看输出结果:
Hello world
1
2
3
DOMContentLoaded

Vite as Vue CLI

Vite 的 就是浏览器原生支持的 ES Module 的方式加载模块,避免开发环境下打包,从而提升开发速度。

Vite 和 Vue CLI 最主要的区别就是:

  • 开发环境:
    • Vite 在开发模式下不需要打包,可以直接运行
      • 使用浏览器原生支持的 ES Module 的方式加载模块(<script type="modue" src="...">),避免开发环境下打包,从而提升开发速度。
      • 因为Vite在开发模式下不需要打包,所以打开项目是秒开的。
    • Vue CLI 开发模式下必须对项目打包才可以运行
  • 生产环境:
    • Vite 在生产环境下使用 Rollup 打包
      • Rollup 基于 ES Module 的方式打包
      • 它不需要使用 babel 把 import 转换成 require,以及一些相应的辅助函数
      • 因此打包的体积会比 webpack 打包的体积更小
      • IE 之外的现代浏览器都已经支持 ES Module 的方式加载模块
    • Vue CLI 使用 webpack 打包

Vite 特点

Vite 开启的开发服务器,它会拦截浏览器发送的请求。

浏览器会向服务器发送请求获取相应的模块。

Vite 会对浏览器不识别的模块进行处理。

比如 import 单文件组件(.vue文件) 时,会在服务器上对 .vue 文件进行编译,把编译的结果返回给浏览器。

使用这个方式,让 Vite 有以下的优点:

  • 快速冷启动
    • 因为不需要打包,可以快速打开项目
  • 按需编译
    • 代码按需编译,因此代码只有在需要加载的时候才会编译,不需要在开启开发服务器的时候等待整个项目被编译
    • 当项目比较大的时候,这个优点就会更明显
  • 模块热更新
    • Vite 支持模块热更新,并且热更新的性能与模块总数无关
    • 无论有多少模块,HMR的速度始终很快

Vite 的使用

创建项目

Vite 有两种创建项目的方式:

  1. 创建基于 Vue 3 的项目
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
  1. 基于模板创建项目,可以支持其他的框架
npm init vite-app --template react

项目结构

创建基于 Vue 3 的项目,查看它的项目结构:

  • /index.html 使用 ESM 加载入口文件
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.js"></script>
</body>
  • /src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'

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

  • /src/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 运行项目,打开页面速度非常快。

Vite 会劫持浏览器请求,处理 .vue 单文件组件,打开开发人员工具查看 App.vue 请求:

  • Vite 开启的web服务器,会劫持 .vue 的请求。
  • 首先会把 .vue 文件解析成 JS 文件
  • 并且把响应头中的 Content-Type 设置成 application/javascript,目的是告诉浏览器,当前发送的文件是 JavaScript 脚本

在这里插入图片描述

  • App.vue 解析的 JS 文件中加载了 App.vue 这个模块,并携带一个 type 参数,所以后面又会发送一次请求
    在这里插入图片描述

App.vue?type=template 请求到服务器后,服务器会把这个单文件组件,通过 Vue 中的模块 compiler-sfc 编译成 render 函数。

编译后的内容,首先把静态节点提升,接着是 render 函数。

在这里插入图片描述

这就是 Vite 的工作原理。

它使用浏览器支持的 ES Module 的方式加载模块,在开发环境下它不会打包项目。

把所有模块的请求,都交给服务器处理,在服务器处理浏览器不能识别的模块。

如果是单文件组件,会调用 compiler-sfc 编译单文件组件,并把编译的结果,返回给浏览器。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是搭建 Vue 3.0 + Vite + Pinia + TypeScript 的步骤: 1. 安装 Node.js,推荐使用 LTS 版本。 2. 安装 Vite: ``` npm init vite-app my-project ``` 这里我们使用 Vite 初始化一个新项目,名称为 my-project。 3. 安装依赖: ``` cd my-project npm install ``` 4. 安装 Vue 3.0: ``` npm install vue@next ``` 5. 安装 Pinia: ``` npm install pinia ``` 6. 安装 TypeScript: ``` npm install --save-dev typescript ``` 7. 配置 TypeScript: 在项目根目录下创建 `tsconfig.json` 文件,内容如下: ```json { "compilerOptions": { "target": "esnext", "module": "esnext", "strict": true, "jsx": "preserve", "sourceMap": true, "moduleResolution": "node", "esModuleInterop": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts", "tests/**/*.tsx"], "exclude": ["node_modules"] } ``` 8. 安装 Pinia Devtools(可选): ``` npm install @pinia/devtools --save-dev ``` 9. 在 `main.ts` 中进行配置: ```typescript import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' const app = createApp(App) // 创建 Pinia 实例 const pinia = createPinia() // 将 Pinia 实例挂载到 app 上 app.use(pinia) app.mount('#app') ``` 10. 编写组件: 在 `src` 目录下创建一个 `components` 目录,然后创建一个 `HelloWorld.vue` 组件: ```vue <template> <div> <h1>Hello, {{ name }}</h1> <button @click="increase">Increase</button> <p>{{ count }}</p> </div> </template> <script lang="ts"> import { defineComponent } from 'vue' import { useStore } from 'pinia' export default defineComponent({ name: 'HelloWorld', setup() { const store = useStore() const name = store.getters.getName const count = store.state.count const increase = () => { store.commit('increase') } return { name, count, increase } }, }) </script> ``` 11. 在 `App.vue` 中使用组件: ```vue <template> <HelloWorld /> </template> <script lang="ts"> import { defineComponent } from 'vue' import HelloWorld from './components/HelloWorld.vue' export default defineComponent({ name: 'App', components: { HelloWorld, }, }) </script> ``` 12. 运行项目: ``` npm run dev ``` 至此,我们已经成功搭建了 Vue 3.0 + Vite + Pinia + TypeScript 的项目。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值