packages 目录下都是独立发布的包(package),可以独立使用。
compiler-*
和编译相关的代码
compiler-core
和平台无关的编译器compiler-dom
浏览器平台下的编译器,依赖于 compiler-core
compiler-sfc
用来编译SFC(单文件组件),依赖于 compiler-core
和 compiler-dom
compiler-ssr
服务端渲染的编译器,依赖于 compiler-dom
reactivity
数据响应式系统,可以独立使用runtime-*
和运行时相关的代码
runtime-core
和平台无关的运行时runtime-dom
针对浏览器的运行时,处理原生 DOM 的 API、事件等runtime-test
是专门为测试而编写的轻量级的运行时
server-renderer
用于服务端渲染shared
Vue 内部使用的公共APIsize-check
是一个私有的包,不会发布到 NPM
template-explorer
是在浏览器里运行的实时编译组件,它会输出 render 函数
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,包含运行时和编译器
global - 全局,这四个文件都可以在浏览器中直接通过 script 标签导入,导入js后,会增加一个全局的 Vue
对象
browser - ESM(浏览器原生模块化方式),在浏览器中可以直接通过 script 标签 type=“module” 的方式导入
bundler - 这两个文件没有打包所有的代码,它们需要配合打包工具来使用。这两个文件都使用 ESM 模块化的方式,内部通过 import 导入了 runtime-core
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 重新演示上面示例的功能
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 中同一功能的代码不需要拆分,有利于代码的提取和重用。
Composition API 提供了一种基于函数的 API,可以更灵活的组织组件的逻辑。
使用 Composition API 可以更合理的组织组件内部的代码结构,还可以把一些逻辑功能,从组件中提取出来,方便其他组件重用。
回顾 Vue 2.x 中编译的过程:
<template>
<div id="app">
<div>static root
<div>static nodediv>
div>
<div>static nodediv>
<div>static nodediv>
<div>{
{ count }}div>
<button @click="handler">buttonbutton>
div>
template>
<div>static root
<div>static nodediv>
div>
Vue 3 通过优化编译的过程和重写虚拟DOM,让首次渲染和更新的性能有了大幅的提升。
Vue 2.x 中通过标记静态根节点,优化 Diff 的过程:
通过 Vue 3 Template Explorer 编译示例代码,查看Vue 3 中在编译时候的优化:
尝试删除最外层的 div:
勾选 Options 中的 hoistStatic 查看效果:
1
代表注释中的 TEXT (文本内容)是动态绑定的,在 Diff 时只会比较文本内容是否发生变化9
代表注释中的 TEXT 和 PROPS 时动态内容,后面还记录了动态绑定的属性名称是 id。
Diff 的过程是最耗时的,在 Vue 2.x 中重新渲染的时候,需要重新创建新旧VNode,Diff 的时候会跳过静态根节点,对比剩下的每一个新旧 VNode,哪怕这个节点什么都没做。
Vue 3 中通过标记和提升静态节点,以及Patch flag 标记动态节点,大大提升了 Diff 的性能
explorer 的 Options 中有一个 cacheHandlers 选项,可以查看缓存事件处理函数的效果。
Vue 3 中将绑定事件的处理函数存储到缓存中,调用时先判断缓存中是否存在,如果存在则调用,不存在则重新获取并存储到缓存中。
查看下面 button 编译结果:
<button @click="clickHandle">按钮button>
查看下面内容的编译结果(手动删除 transition
和 v-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'
Vue 3 的作者开发了一个自己的构建工具 Vite。
这个单词来源于法语,快
的意思,意味着 Vite 比过去基于 webpack 的 Vue CLI 更快。
使用 Vite 在开发阶段,测试项目的时候,不需要打包,可以直接运行项目,提升了开发的效率。
标签导入模块
module
的 script
标签默认是延迟加载的
script
标签设置 defer
<script>
window.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded')
})
script>
<script type="module" src="./modules/index.js">script>
<div id="app">Hello worlddiv>
// ./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 的 快
就是浏览器原生支持的 ES Module 的方式加载模块,避免开发环境下打包,从而提升开发速度。
Vite 和 Vue CLI 最主要的区别就是:
),避免开发环境下打包,从而提升开发速度。Vite 开启的开发服务器,它会拦截浏览器发送的请求。
浏览器会向服务器发送请求获取相应的模块。
Vite 会对浏览器不识别的模块进行处理。
比如 import 单文件组件(.vue
文件) 时,会在服务器上对 .vue
文件进行编译,把编译的结果返回给浏览器。
使用这个方式,让 Vite 有以下的优点:
Vite 有两种创建项目的方式:
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
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 请求:
.vue
的请求。.vue
文件解析成 JS 文件Content-Type
设置成 application/javascript
,目的是告诉浏览器,当前发送的文件是 JavaScript 脚本App.vue?type=template
请求到服务器后,服务器会把这个单文件组件,通过 Vue 中的模块 compiler-sfc
编译成 render 函数。
编译后的内容,首先把静态节点提升,接着是 render 函数。
这就是 Vite 的工作原理。
它使用浏览器支持的 ES Module 的方式加载模块,在开发环境下它不会打包项目。
把所有模块的请求,都交给服务器处理,在服务器处理浏览器不能识别的模块。
如果是单文件组件,会调用 compiler-sfc
编译单文件组件,并把编译的结果,返回给浏览器。