Vue3 特点以及优势-Vue3.4源码解剖
Vue3 特点以及优势
1.声明式框架
命令式和声明式区别
- 早在 JQ 的时代编写的代码都是命令式的,命令式框架重要特点就是关注过程
- 声明式框架更加关注结果。命令式的代码封装到了 Vuejs 中,过程靠 vuejs 来实现
声明式代码更加简单,不需要关注实现,按照要求填代码就可以
- 命令式编程:
let numbers = [1,2,3,4,5]
let total = 0
for(let i = 0; i < numbers.length; i++) {
total += numbers[i] - 关注了过程
}
console.log(total)
- 声明式编程:
let total2 = numbers.reduce(function (memo,current) {
return memo + current
},0)
console.log(total2)
2.采用虚拟 DOM
传统更新页面,拼接一个完整的字符串 innerHTML 全部重新渲染,添加虚拟 DOM 后,可以比较新旧虚拟节点,找到变化在进行更新。虚拟 DOM 就是一个对象,用来描述真实 DOM 的
const vnode = {
__v_isVNode: true,
__v_skip: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
children,
component: null,
el: null,
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null,
};
3.区分编译时和运行时
- 我们需要有一个虚拟 DOM,调用渲染方法将虚拟 DOM 渲染成真实 DOM (缺点就是虚拟 DOM 编写麻烦)
- 专门写个编译时可以将模板编译成虚拟 DOM (在构建的时候进行编译性能更高,不需要再运行的时候进行编译,而且 vue3
在编译中做了很多优化)
4.Vue3 设计思想
- Vue3.0 注重模块上的拆分 Vue3 中的模块之间耦合度低,模块可以独立使用。 拆分模块
- 通过构建工具 Tree-shaking 机制实现按需引入,减少用户打包后体积。 组合式 API
- Vue3 允许自定义渲染器,扩展能力强。 扩展更方便
- 使用 RFC 来确保改动和设计都是经过 Vuejs 核心团队探讨并得到确认的。也让用户可以了解每一个功能采用或废弃的前因后果。 采用RFC
Vue3整体架构
Monorepo 管理项目
Monorepo 是管理项目代码的一个方式,指在一个项目仓库(repo)中管理多个模块/包(package)。 Vue3 源码采用 monorepo 方式进行管理,将模块拆分到 package 目录中。作为一个个包来管理,这样职责划分更加明确。
- 个仓库可维护多个模块,不用到处找仓库
- 方便版本管理和依赖管理,模块之间的引用,调用都非常方便
1.Vue3 项目结构
2.Vue3 采用 Typescript
复杂的框架项目开发,使用类型语言非常有利于代码的维护,在编码期间就可以帮我们做类型检查,避免错误。所以 TS 已经是主流框架的标配~
Vue2 早期采用 Flow 来进行类型检测 (Vue2 中对 TS 支持并不友好), Vue3 源码采用 Typescript 来进行重写。同时 Vue2.7 也采用 TS 进行重写。TS 能对代码提供良好的类型检查,同时也支持复杂的类型推导。
搭建 Monorepo 环境
Vue3 中使用pnpm workspace来实现monorepo (pnpm是快速、节省磁盘空间的包管理器。主要采用符号链接的方式管理模块)
1.全局安装 pnpm
npm install pnpm -g # 全局安装pnpm
pnpm init # 初始化配置文件
2.创建.npmrc 文件
shamefully-hoist = true
这里您可以尝试一下安装Vue3, pnpm install vue此时默认情况下vue3中依赖的模块不会被提升到node_modules下。 添加羞耻的提升可以将 Vue3,所依赖的模块提升到node_modules中
3.配置 workspace
新建 pnpm-workspace.yaml
packages:
- "packages/*"
将 packages 下所有的目录都作为包进行管理。这样我们的 Monorepo 就搭建好了。确实比lerna + yarn workspace更快捷
4.环境搭建
打包项目 Vue3 采用 rollup 进行打包代码,安装打包所需要的依赖
pnpm install typescript minimist esbuild -D -w
5.初始化 TS
pnpm tsc --init
先添加些常用的ts-config配置,后续需要其他的在继续增加
{
"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
}
}
6.创建模块
packages目录下新建两个 package
- reactivity 响应式模块
- shared 共享模块
所有包的入口均为src/index.ts
这样可以实现统一打包
- reactivity/package.json
{
"name": "@vue/reactivity",
"version": "1.0.0",
"main": "index.js",
"module": "dist/reactivity.esm-bundler.js",
"unpkg": "dist/reactivity.global.js",
"buildOptions": {
"name": "VueReactivity",
"formats": ["esm-bundler", "cjs", "global"]
}
}
- shared/package.json
{
"name": "@vue/shared",
"version": "1.0.0",
"main": "index.js",
"module": "dist/shared.esm-bundler.js",
"buildOptions": {
"formats": ["esm-bundler", "cjs"]
}
}
formats 为自定义的打包格式
- lobal 立即执行函数的格式,会暴露全局对象
- esm-browser 在浏览器中使用的格式,内联所有的依赖项。
- esm-bundler 在构建工具中使用的格式,不提供.prod 格式,在构建应用程序时会被构建工具一起进行打包压缩。
- cjs 在 node 中使用的格式,服务端渲染。
pnpm install @vue/shared --workspace --filter @vue/reactivity
配置ts引用关系
"baseUrl": ".",
"paths": {
"@vue/*": ["packages/*/src"]
}
7.开发环境esbuild打包
创建开发时执行脚本, 参数为要打包的模块
解析用户参数
"scripts": {
"dev": "node scripts/dev.js reactivity -f esm"
}
import esbuild from "esbuild"; // 打包工具
import minimist from "minimist"; // 命令行参数解析
import { resolve, dirname } from "path";
import { fileURLToPath } from "url";
import { createRequire } from "module";
const require = createRequire(import.meta.url); // 可以在es6中使用require语法
const args = minimist(process.argv.slice(2)); // 解析打包格式和打包模块
const format = args.f || "iife";
const target = args._[0] || "reactivity";
// __dirname在es6模块中不存在需要自行解析
const __dirname = dirname(fileURLToPath(import.meta.url));
const pkg = require(`../packages/${target}/package.json`);
esbuild
.context({
entryPoints: [resolve(__dirname, `../packages/${target}/src/index.ts`)],
outfile: resolve(
// 输出的文件
__dirname,
`../packages/${target}/dist/${target}.js`
),
bundle: true, // 全部打包
sourcemap: true, // sourcemap源码映射
format, // 打包格式 esm , cjs, iife
globalName: pkg.buildOptions?.name, // 全局名配置
platform: "browser", // 平台
})
.then((ctx) => {
console.log("watching~~~");
return ctx.watch(); // 监控文件变化
});
Vue3 响应式数据核心
Vue3 中使用 Proxy 来实现响应式数据变化。
CompositionAPI
简单的组件仍然可以采用 OptionsAPI 进行编写(但是在 Vue3 中基本不在使用),compositionAPI 在复杂的逻辑中有着明显的优势~
- CompositionAPI 在用户编写复杂业务逻辑不会出现反复横跳问题
- CompositionAPI 不存在this指向不明确问题
- Composition API 对 tree-shaking 更加友好,代码也更容易压缩。
- CompositionAPI 提取公共逻辑非常方便
reactivity模块中就包含了很多我们经常使用到的API 例如:computed、reactive、ref、effect 等