全面拥抱 Vue 3
在 2022 年 2 月 7 日,Vue 3
成为vue的默认版本。Vue 2.7
成为 Vue 2.x
的最终次要版本。可以预见:在不久的将来,Vue 2.x
将会逐步淡出江湖,Vue 3
将会成为代替 Vue 2
的主流框架。
Vue 3
整体的设计相对于 Vue 2.x
有着不小的变化,我们先着重介绍几个相对于 Vue 2.x
而言较大的几个变化,这里的介绍不会深入到源码的细节。
1. 源码组织上的变化
Vue 3
相对于 Vue 2
使用 monorepo
的方式进行包管理,使得 Vue 3
源码模块职责显得特别地清晰明了,每个包独立负责一块核心功能的实现,方便开发和测试。
2. 引入 Composition API
相对而言,Composition API
更适用于大型的项目,因为大型项目可能会产生大量状态逻辑的维护,甚至跨组件的逻辑复用;而对于中小型项目来说,Options API
可以在你写代码时减少思考组织状态逻辑的方式,也是一种不错的选择。
3. 运作机制的变化
vue 2
的运行机制如下:
大致流程为:
- 先通过
new Vue()
创建组件实例; - 然后在创建组件实例过程中进行
initState
、options
合并、initLifecycle
、initRender
、$mount
; - 并且将模板转化成
render
函数,最后执行render
函数,进行依赖收集; - 当依赖发生改变时,触发相关的
watcher
重新执行。
Vue 3
则在底层实现中,摒弃了 Vue 2
的部分实现,采用全新的响应式模型进行重写。vue 3
的运行机制如下:
可以看到 vue 3 的几个重要变化
- 首先,之前通过
new Vue()
来创建 Vue 对象的方式已经变成了createApp
; - 其次,在响应式部分也由原来的
Object.defineProperty
改成了现在的Proxy API
实现; - 另外,针对响应式依赖收集的内容,在
Vue 2.x
版本中是收集了Watcher
,而到了Vue 3
中则变成了effect
。
4. 其他方面的变化
除了上面所说的这些变化外,Vue 3
不管是在编译时、还是在运行时都做了大量的性能优化。例如,在编译时,Vue 3
通过标记 /*#__PURE__*/
来为打包工具提供良好的 Tree-Shaking
机制,通过 静态提升 机制,避免了大量静态节点的重复渲染执行;在运行时,又通过批量队列更新机制优化了更新性能,通过 PatchFlags
和 dynamicChildren
进行了 diff
的靶向更新……
环境搭建
- 创建文件夹
mini-vue3-zf-pnpm
,并初始化项目pnpm init
- 创建
packages/reactivity/src/index.ts
、packages/shared/src/index.ts
两个文件夹 - 创建
.npmrc
配置文件# pnpm安装vue时,默认不会将vue依赖的包展平到 node_modules 下,导致项目中无法使用vue的依赖 # 通过以下配置将 vue 依赖的包展平到 node_modules 下 shamefully-hoist = true
- 创建
pnpm-workspace.yaml
,定义工作空间的根目录packages: - "packages/*"
- 安装
esbuild
、typescript
、minimist
(minimist
是一个专门用于处理Node.js
启动参数的库)pnpm install esbuild typescript minimist -D -w
- 初始化
typescript
配置文件pnpm tsc --init
{ "compilerOptions": { "outDir": "dist", // 输出的目录 "sourceMap": true, // 采用 sourcemap "target": "es2016", // 目标语法 "module": "esnext", // 模块格式 "moduleResolution": "node", // 解析模块的方式 "strict": false, // 严格模式 "resolveJsonModule": true, // 解析json模块 "esModuleInterop": true, // 允许通过es6语法引入 commonjs 模块 "jsx": "preserve", // jsx 不转义 "lib": ["esnext", "dom"] // 支持的类库:esnext和dom } }
- 进入
reactivity
模块,初始化子项目
修改生成的cd packages cd reactivity pnpm init
package.json
:{ "name": "@vue/reactivity", "version": "1.0.0", "description": "", "module": "dist/reactivity.esm-bundler.js", "unpkg": "dist/reactivity.global.js", "buildOptions": { "name": "VueReactivity", "formats": [ "esm-browser", "esm-bundler", "cjs", "global" ] } }
formats 为自定义的需要打包的格式:
global
:立即执行函数的格式,会暴露全局对象esm-browser
:在浏览器中使用的格式,内联所有的依赖项esm-bundler
:在构建工具中使用的格式,不提供.prod
格式,在构建应用程序时会被构建工具一起进行打包压缩cjs
:在 node 中使用的格式,例如服务端渲染
- 同理,也进入
shared
模块,修改生成的package.json
:{ "name": "@vue/shared", "version": "1.0.0", "module": "dist/shared.esm-bundler.js", "buildOptions": { "formats": [ "esm-bundler", "cjs" ] } }
- 在
reactivity
模块安装shared
模块的依赖:pnpm install @vue/shared@workspace --filter @vue/reactivity
- 在
tsconfig.json
中配置路径别名:"baseUrl": ".", "paths": { "@vue/*": ["packages/*/src"] }
- 对模块进行打包
- 在根
package.json
中添加命令:"dev": "node scripts/dev.js reactivity -f esm"
- 创建
scripts/dev.js
:const { build } = require("esbuild"); const path = require("path"); const args = require("minimist")(process.argv.slice(2)); // { _: [ 'reactivity' ], f: 'esm' } // 打包的模块是哪个 const target = args._[0] || "reactivity"; // 打包的格式是什么 const format = args.f || "global"; // 读取模块的 package.json 文件 const pkg = require(path.resolve( __dirname, `../packages/${target}/package.json` )); // 输出格式:把 global 改成 iife const outputFormat = format.startsWith("global") ? "iife" : format === "cjs" ? "cjs" : "esm"; // 输出的路径 const outfile = path.resolve( __dirname, `../packages/${target}/dist/${target}.${format}.js` ); // 使用esbuild打包 build({ entryPoints: [path.resolve(__dirname, `../packages/${target}/src/index.ts`)], // 入口 outfile, // 出口 bundle: true, // 是否打包到一起 sourcemap: true, // 是否生成sourcemap文件(.map 结尾) format: outputFormat, // 打包的格式 globalName: pkg.buildOptions?.name, // 打包的全局名称 platform: format === "cjs" ? "node" : "browser", // 平台 watch: { // 监控文件变化 onRebuild(error) { if (!error) { console.log("rebuild~~~"); } }, }, }).then(() => { console.log("watching~~~"); });
- 在根
接下来,只需要执行 pnpm run dev
即可对 reactivity
模块打包,并在 reactivity/dist
目录下生成打包好的文件。
总结
本小节,我们阐述了 vue 3
相较于 vue 2
的几个重大改变,并从零搭建了 mini-vue3
的开发环境。最后,让我们一起进入 Vue 3
的世界,探索其中的奥秘吧!