feat:更新文档架构

This commit is contained in:
2026-05-20 11:26:38 +08:00
parent ad9a4d6806
commit 8c9b3adc57
46 changed files with 218 additions and 75 deletions

View File

@@ -0,0 +1,367 @@
---
title: Vue3架构原理
date: 2025-12-15 23:25
---
# 一、Vue3 整体架构图
```mermaid
graph TD
A[Vue 3] --> B[Compiler]
A --> C[Runtime]
A --> D[Reactivity]
B --> B1[Parser]
B --> B2[Transformer]
B --> B3[Codegen]
C --> C1[Virtual DOM]
C --> C2[Renderer]
C --> C3[Component]
D --> D1[Proxy]
D --> D2[Effect]
D --> D3[Dependency Graph]
B3 -->|生成| C1
C1 -->|Diff/Patch| C2
D3 -->|驱动更新| C1
```
## 1.1 编译器模块
  编译器负责将模板转换为可执行的渲染函数,包含**解析器Parser、转换器Transformer和代码生成器Codegen** 三个子模块。
- compiler-core: 核心编译器负责基础的编译功能如生成抽象语法树AST、基础的模板编译等是平台无关的编译器核心。
- compiler-dom: 基于compiler-core针对浏览器环境的编译模块重写了编译和解析方法处理与原生 DOM API 相关的编译工作。
- compiler-sfc: 用于处理 Vue 单文件组件(.vue的编译将单文件组件中的模板、脚本和样式进行解析和编译。
- compiler-ssr服务端渲染的编译模块负责在服务端将 Vue 组件编译为适合服务器端渲染的代码。
## 1.2 运行时模块
  运行时系统处理**虚拟DOM的创建、更新和渲染**包含组件实例管理、虚拟DOM diff算法和平台渲染器。
- runtime-core运行时核心模块包含了 Vue 组件的生命周期、虚拟节点vnode、观察者watch等核心 API是平台无关的运行时核心。
- runtime-dom运行时与 DOM 相关的核心模块,提供了与浏览器 DOM 交互的功能,如创建应用实例、操作 DOM 元素等。
## 1.3 响应式系统
  响应式系统基于Proxy实现数据劫持通过依赖收集和触发更新机制驱动视图变化。
  三大系统协同工作:编译器生成的渲染函数由运行时执行,响应式系统追踪数据变化触发组件更新,形成完整的渲染闭环。
# 二、基础模块
## 2.1 响应式系统
```mermaid
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(原始对象):::process -->|Proxy 包装| B(响应式对象):::process
B -->|属性读取| C(依赖收集):::process
B -->|属性修改| D(触发更新):::process
C --> E(副作用函数):::process
D --> E
E -->|更新视图| F(真实 DOM):::process
```
  Vue3的响应式系统通过Proxy代理对象属性在get操作时通过track函数收集当前正在执行的副作用如组件渲染函数作为依赖存储到依赖关系图中。当set操作发生时trigger函数根据依赖关系图找到所有关联的副作用函数通过调度器批量执行。
  这种设计使得数据变化能精准触发组件更新,同时通过异步批处理避免重复渲染。
  响应式系统独立于其他模块可单独使用为Vue3提供了高效的状态管理能力。
## 2.2 虚拟 DOM
```mermaid
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(旧虚拟 DOM 树):::process -->|Diff 算法| B(差异比较):::process
C(新虚拟 DOM 树):::process --> B
B -->|更新操作| D(真实 DOM):::process
```
  当组件更新时Vue会比较新旧虚拟DOM树的差异。通过Patch Flags快速识别动态节点类型如文本、样式等仅对标记为动态的部分进行比较对于带key的子节点列表使用最长递增子序列算法最小化DOM操作完全静态的子树则直接跳过diff过程。
  这种靶向更新策略将传统虚拟DOM算法O(n³)的时间复杂度优化到接近O(n),同时配合异步批量更新机制,确保复杂应用也能保持流畅渲染
## 2.3 编译器
```mermaid
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(模板字符串):::process -->|解析| B(抽象语法树 - AST):::process
B -->|转换| C(优化后的 AST):::process
C -->|生成| D(渲染函数代码):::process
```
  Vue编译器将模板字符串转换为渲染函数经历解析、转换和代码生成三个阶段。
  解析阶段通过词法分析生成原始AST抽象语法树转换阶段对AST进行优化包括静态节点提升将不变节点提取到渲染函数外部、补丁标记给动态节点添加优化标识、树结构打平减少动态子节点的嵌套层级最后代码生成阶段输出优化的渲染函数。
  这些优化使得运行时能跳过静态内容比较,直接定位动态节点,大幅提升渲染性能。
## 2.4 渲染器
```mermaid
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(虚拟 DOM 树):::process -->|创建节点| B(真实 DOM 节点):::process
B -->|插入节点| C(页面):::process
A -->|更新节点| B
```
  Vue渲染器是连接虚拟DOM与真实平台的桥梁分为虚拟DOM处理层、Patch算法层和平台操作层。
  它通过抽象平台API如createElement、insert等实现跨平台能力开发者可配置不同平台的底层操作来支持Web、Canvas甚至原生应用。
  渲染器内部采用增量更新策略结合编译器提供的优化提示如静态标记、动态子节点索引确保DOM操作最精简。
  这种设计使Vue在保证Web端高性能的同时也能灵活适配各种渲染环境。
# 三、Vue3应用生命周期
## 3.1 初始化阶段
  流程:
1. 调用`createApp()`创建应用实例
2. 创建平台专属渲染器DOM/SSR/Test
3. 初始化应用上下文和插件系统
  关键代码:
```typescript
// runtime-dom/index.ts
const renderer = createRenderer({
patchProp, // DOM属性操作
...nodeOps // DOM节点操作集合
})
function createApp(rootComponent) {
const context = createAppContext()
const app = {
_component: rootComponent,
mount(container) {
const vnode = createVNode(rootComponent)
renderer.render(vnode, container)
return vnode.component.proxy
}
}
return app
}
```
## 3.2 组件挂载阶段
  流程:
1. 标准化容器(字符串选择器 → DOM元素
2. 创建根组件虚拟节点vnode
3. 初始化组件解析props和slots、执行setup函数、建立响应式连接等
4. 建立渲染effect为组件创建响应式的副作用函数
  关键代码:
```typescript
// runtime-core/renderer.ts
function mountComponent(vnode, container) {
// 1. 创建组件实例
const instance = createComponentInstance(vnode)
// 2. 初始化组件
setupComponent(instance)
// 3. 建立渲染effect
setupRenderEffect(instance, container)
}
function setupRenderEffect(instance, container) {
instance.update = effect(() => {
if (!instance.isMounted) {
// 首次渲染
const subTree = (instance.subTree = renderComponentRoot(instance))
patch(null, subTree, container)
instance.isMounted = true
} else {
// 更新渲染
const nextTree = renderComponentRoot(instance)
patch(instance.subTree, nextTree, container)
instance.subTree = nextTree
}
}, { scheduler: queueJob })
}
```
## 3.3 响应式更新阶段
  流程:
1. 数据变更触发Proxy的set拦截
2. 通过trigger触发依赖的effect
3. 调度器将更新任务加入队列
4. 下一个tick执行队列中的任务
5. 执行组件render函数生成新vnode
6. 调用patch进行差异更新
  关键代码:
```typescript
// reactivity/reactive.ts
function createReactiveObject(target) {
return new Proxy(target, {
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (hasChanged(value, oldValue)) {
trigger(target, key) // 触发更新
}
return result
}
})
}
// runtime-core/scheduler.ts
const queue = []
function queueJob(job) {
if (!queue.includes(job)) {
queue.push(job)
queueFlush()
}
}
function queueFlush() {
if (!isFlushing) {
nextTick(flushJobs)
}
}
function flushJobs() {
isFlushing = true
queue.sort((a, b) => a.id - b.id) // 保证父组件先更新
for (let i = 0; i < queue.length; i++) {
queue[i]()
}
queue.length = 0
isFlushing = false
}
```
## 3.4 虚拟DOM Patch阶段
&emsp;&emsp;流程:
1. 比较新旧vnode的类型
2. 不同类型:卸载旧节点,挂载新节点
3. 相同类型:更新节点
4. 子节点对比采用高效diff算法
&emsp;&emsp;关键代码:
```typescript
// runtime-core/renderer.ts
function patch(n1, n2, container) {
// 1. 类型不同直接卸载
if (n1 && !isSameVNodeType(n1, n2)) {
unmount(n1)
n1 = null
}
const { type, patchFlag } = n2
// 2. 根据类型处理
switch (type) {
case Text:
processText(n1, n2, container)
break
case Fragment:
processFragment(n1, n2, container)
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(n1, n2, container)
} else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(n1, n2, container)
}
}
}
function processElement(n1, n2, container) {
if (n1 == null) {
mountElement(n2, container)
} else {
patchElement(n1, n2)
}
}
function patchElement(n1, n2) {
// 根据patchFlag进行优化更新
if (n2.patchFlag & PatchFlags.FULL_PROPS) {
// 全量props更新
} else {
// 只更新动态props
}
// 更新子节点
patchChildren(n1, n2)
}
```
## 3.5 组件卸载阶段
&emsp;&emsp;流程:
1. 触发beforeUnmount生命周期钩子
2. 停止组件渲染effect
3. 递归卸载子组件
4. 移除DOM节点
5. 触发unmounted生命周期钩子
6. 清理响应式依赖
&emsp;&emsp;关键代码:
```typescript
// runtime-core/renderer.ts
function unmount(vnode) {
if (vnode.type === Fragment) {
unmountChildren(vnode.children)
return
}
const { component } = vnode
if (component) {
unmountComponent(component)
} else {
remove(vnode.el!)
}
}
function unmountComponent(instance) {
// 1. 触发beforeUnmount
if (instance.bum) {
invokeArrayFns(instance.bum)
}
// 2. 停止响应式effect
stop(instance.update)
// 3. 递归卸载子树
unmount(instance.subTree)
// 4. 触发unmounted
if (instance.um) {
queuePostRenderEffect(instance.um)
}
// 5. 清理引用
instance.isUnmounted = true
}
```
## 3.6 编译阶段
&emsp;&emsp;流程:
1. 将模板解析为AST
2. 转换AST静态提升/标记PatchFlag
3. 生成可执行的渲染函数代码
4. 运行时直接使用优化后的渲染函数
&emsp;&emsp;关键代码:
```typescript
// compiler-core/src/compile.ts
function baseCompile(template, options) {
// 1. 解析阶段
const ast = parse(template)
// 2. 转换阶段
transform(ast, {
hoistStatic: true, // 静态提升
nodeTransforms: [
transformIf,
transformFor,
transformExpression
]
})
// 3. 代码生成
const code = generate(ast, {
mode: 'function',
runtimeGlobalName: 'Vue',
prefixIdentifiers: true
})
return {
ast,
code: `with(this){return ${code}}` // 生成渲染函数
}
}
```

419
docs/Web/Vue/Vue3-Common.md Normal file
View File

@@ -0,0 +1,419 @@
---
title: Vue3-Common
date: 2025-12-15 23:25
---
# 一、依赖管理
&emsp;&emsp;本项目会传递安装的依赖有:
| 包名称 | 版本 | 含义和用途说明 |
|-------|------|---------------|
| `@fortawesome/fontawesome-free` | ^6.7.2 | FontAwesome 图标库的免费版本 |
| `@popperjs/core` | ^2.11.8 | 工具提示和弹出框定位引擎 |
| `axios` | ^1.4.0 | 基于 Promise 的 HTTP 客户端 |
| `compressorjs` | ^1.2.1 | 纯 JavaScript 图片压缩库 |
| `crypto-js` | ^4.2.0 | JavaScript 加密算法库 |
| `dayjs` | ^1.11.13 | 轻量级的日期处理库 |
| `echarts` | ^5.5.1 | 百度开源的数据可视化图表库 |
| `element-plus` | ^2.6.0 | 基于 Vue 3 的桌面端 UI 组件库 |
| `js-cookie` | ^3.0.5 | JavaScript Cookie 操作库 |
| `lunar-calendar` | ^0.1.4 | 农历日历转换库 |
| `lunar-javascript` | ^1.6.13 | 农历日期处理的 JavaScript 库 |
| `path-browserify` | ^1.0.1 | Node.js path 模块的浏览器版本兼容实现 |
| `qs` | ^6.13.0 | URL 查询字符串解析和序列化库 |
| `v-calendar` | ^3.1.2 | Vue.js 的日历和日期选择器组件 |
::: tip
如果使用pnpm安装会传递peerDependencies部分。
:::
::: warning
建议将Vite、Typescript、@types等构建工具依赖放在devDependencies中
:::
# 二、vite.config.ts配置
## 2.1 生成Typescript类型文件
&emsp;&emsp;安装vite-plugin-dts插件
```cmd
pnpm add vite-plugin-dts -D
```
```typescript
import dts from 'vite-plugin-dts'
// 打包输出文件夹
const outDirPath = 'dist'
// 需要打包的类型文件
const TARGET_TYPE_FOLDERS = ['src/components', 'src/utils', 'src/types', 'src/vue3-common.ts']
export default defineConfig({
plugins: [
// 生成类型文件
dts({
// 需要处理的文件
include: TARGET_TYPE_FOLDERS,
// 使用特定的 tsconfig 配置
tsconfigPath: path.resolve(__dirname, 'tsconfig.app.json'),
// 类型文件输出目录
outDir: path.resolve(__dirname, outDirPath)
})
],
})
```
&emsp;&emsp;打包后会在dist目录生成.d.ts文件提供完整的Typescript类型支持。
## 2.2 build打包配置
```typescript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import dts from 'vite-plugin-dts'
import path from 'path'
import fs from 'fs'
import { ICommonObj } from './src/types'
// 打包输出文件夹
const outDirPath = 'dist'
// 需要单个打包的文件夹(组件和工具类)
const TARGET_LIB_FOLDERS = ['src/components', 'src/utils']
// 需要排除的第三方依赖
const EXTERNAL = ['vue', 'element-plus', 'echarts', 'axios', 'moment', 'crypto-js', 'spark-md5', 'path-browserify']
/**
* 递归读取文件夹下的所有文件
* @param folderPath 文件夹
*/
const getFilesFromFolder = (folderPath: string) => {
const files: string[] = []
// 读取文件夹中的内容
fs.readdirSync(folderPath).forEach((item: string) => {
const fullPath = path.join(folderPath, item)
// 读取文件状态 如果是文件夹,递归读取
if (fs.statSync(fullPath).isDirectory()) {
files.push(...getFilesFromFolder(fullPath))
} else {
// 否则添加至文件列表
files.push(fullPath)
}
})
return files
}
// 动态生成入口文件
const inputEntries = TARGET_LIB_FOLDERS.reduce((entries: ICommonObj, folder) => {
// 获取文件夹内的所有文件
const files = getFilesFromFolder(path.resolve(__dirname, folder))
files.forEach((filePath) => {
// 判断文件是否是 .vue 或 .ts 文件
if (filePath.endsWith('.vue') || filePath.endsWith('.ts')) {
// 获取相对路径并保持目录结构
const relativePath = path.relative('src', filePath)
const entryName = path.join('lib', relativePath.replace(/\.(vue|ts)$/, ''))
entries[entryName] = filePath // 为每个文件创建一个入口
}
})
return entries
}, {})
// https://vitejs.dev/config/
export default defineConfig({
build: {
// lib文件配置
lib: {
// 入口文件
entry: path.resolve(__dirname, 'src/vue3-common.ts'),
formats: ['es'],
name: 'vue3-common',
// 文件名
fileName: (format) => `vue3-common.${format}.js`
},
// 输出文件路径
outDir: outDirPath,
// 是否将css文件分割
cssCodeSplit: true,
// Rollup打包配置
rollupOptions: {
// 需要排除的依赖 通常为第三方库
external: EXTERNAL,
// 输入配置
input: {
// 入口文件
'vue3-common': path.resolve(__dirname, 'src/vue3-common.ts'),
// 其余需要单独打包的文件
...inputEntries
},
// 输出配置
output: {
dir: path.resolve(__dirname, outDirPath),
// 入口文件
entryFileNames: '[name].js',
// chunk文件
chunkFileNames: 'lib/[name].js',
// 资源文件
assetFileNames: 'styles/[name].[ext]'
}
},
// 是否压缩代码
minify: false
}
})
```
&emsp;&emsp;打包后会在dist目录生成源码文件。
# 三、package.json配置
## 3.1 导出路径
```json
{
"exports": {
".": {
"import": "./dist/vue3-common.js",
"require": "./dist/vue3-common.js",
"types": "./dist/vue3-common.d.ts"
},
"./components/SvgIcon.vue": {
"import": "./dist/lib/components/SvgIcon.js",
"types": "./dist/components/SvgIcon.vue.d.ts"
},
"./styles/SvgIcon.css": "./dist/styles/SvgIcon.css",
"./components/LoginForm.vue": {
"import": "./dist/lib/components/LoginForm.js",
"types": "./dist/components/LoginForm.vue.d.ts"
},
"./styles/LoginForm.css": "./dist/styles/LoginForm.css",
"./components/MenuItem.vue": {
"import": "./dist/lib/components/MenuItem.js",
"types": "./dist/components/MenuItem.vue.d.ts"
},
"./components/MenuList.vue": {
"import": "./dist/lib/components/MenuList.js",
"types": "./dist/components/MenuList.vue.d.ts"
},
"./components/Hamburger.vue": {
"import": "./dist/lib/components/Hamburger.js",
"types": "./dist/components/Hamburger.vue.d.ts"
},
"./styles/Hamburger.css": "./dist/styles/Hamburger.css",
"./components/MultiInput.vue": {
"import": "./dist/lib/components/MultiInput.js",
"types": "./dist/components/MultiInput.vue.d.ts"
},
"./styles/MultiInput.css": "./dist/styles/MultiInput.css",
"./types": {
"types": "./dist/types/index.d.ts"
},
"./utils/axiosUtil": {
"import": "./dist/lib/utils/axiosUtil.js",
"types": "./dist/types/utils/axiosUtil.d.ts"
},
"./utils/cryptoUtil": {
"import": "./dist/lib/utils/cryptoUtil.js",
"types": "./dist/types/utils/cryptoUtil.d.ts"
},
"./utils/dataUtil": {
"import": "./dist/lib/utils/dataUtil.js",
"types": "./dist/types/utils/dataUtil.d.ts"
},
}
}
```
&emsp;&emsp;在项目中引入common包后通过配置tsconfig.json即可实现按需引入功能`import { fun1 } from 'vue3-common/utils/index'`
```json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"vue3-common/*": ["node_modules/vue3-common/dist/*"]
}
}
}
```
# 四、发布框架
1. 发布到本地
执行`npm pack`命令,会在项目文件夹下生成`.tgz`文件,其他项目通过文件路径形式引入:
```json
"vue3-common": "file:../vue3-common/vue3-common-1.0.0.tgz"
```
2. 发布到git
将dist文件夹发布到git仓库其他项目通过git形式引入
```json
"vue3-common": "git+https://gitee.com/Cxx0822/vue3-common#master"
```
# 五、组件使用说明
## 5.1 菜单组件
1. 安装Vue Router
```cmd
pnpm install vue-router
```
2. 在src/views文件夹中新建vue文件例如
```cmd
views
viewA
index.vue
viewB
index.vue
```
3. 在src/views文件夹下新建meta.ts配置文件
```typescript
import type { IRouteMetaConfig } from 'vue3-common/types'
export const metaList: IRouteMetaConfig = {
'/viewA': {
title: 'viewA',
icon: 'viewA',
order: 1,
redirect: '/viewA/index'
},
'/viewA/index': {
title: 'viewA-Index',
icon: ''
},
'/viewB': {
title: 'viewB',
icon: 'viewB',
order: 2,
redirect: '/viewB/index'
},
'/viewB/index': {
title: 'viewB-index',
icon: ''
}
}
```
&emsp;&emsp;order对应的显示顺序关系icon对应的为src/icons/svg中的svg图标文件参考下文图标组件
4. 在src目录新建router文件夹新建menu.ts文件
```typescript
import { getRoutersByModules, sortRoutesByOrder } from 'vue3-common/utils/routerUtil'
import Layout from '@/layout/index.vue'
import { metaList } from '@/views/meta'
const modules = {
...import.meta.glob('@/views/viewA/**/*.vue'),
...import.meta.glob('@/views/viewB/**/*.vue'),
}
const menuRoutes = getRoutersByModules(modules, Layout, metaList)
export default sortRoutesByOrder(menuRoutes)
```
&emsp;&emsp;导入刚才的views文件夹并生成路由菜单。
5. 如果还有其他的常量路由可以在src/router中新建constant.ts文件
```typescript
import type { RouteRecordRaw } from 'vue-router'
const constantRoutes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/login'
},
// 主页
{
path: '/login',
component: () => import('@/views/login/index.vue'),
meta: { hidden: true }
}
]
export default constantRoutes
```
&emsp;&emsp;该部分即Vue Router中的路由定义。
6. 在src/router中新建index.ts
```typescript
import { createRouter, createWebHashHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
import { setupRouteGuard } from 'vue3-common/utils/permissionUtil'
// 使用 import.meta.glob 自动导入所有 src/router 目录下的 .ts 文件
const routeModules = import.meta.glob('./*.ts', { eager: true })
// 将所有模块的默认导出(即路由配置)合并成一个路由数组
const routes: RouteRecordRaw[] = Object.values(routeModules)
.map((module: any) => module.default) // 获取每个模块的默认导出
.flat() // 扁平化数组,确保所有路由项都在一个数组中
const router = createRouter({
history: createWebHashHistory(),
scrollBehavior: () => ({ top: 0 }),
routes
})
// 设置路由守卫
setupRouteGuard(router)
export default router
```
&emsp;&emsp;遍历src/router文件夹下所有的路由文件并添加路由守卫。
7. 在main.ts中配置路由
```ts
// 引入路由
import router from './router'
// 创建Vue3实例
const app = createApp(App)
// 使用路由
app.use(router)
```
## 5.2 图标组件
1. 安装vite-plugin-svg-icons依赖
```cmd
pnpm install vite-plugin-svg-icons -D
```
2. 配置vite.config.ts
```typescript
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
export default defineConfig({
plugins: [
vue(),
createSvgIconsPlugin({
// 指定 SVG图标 保存的文件夹路径
iconDirs: [path.resolve(process.cwd(), 'src/icons/svg')],
// 指定 使用svg图标的格式
symbolId: 'icon-[dir]-[name]'
})
],
})
```
3. 在main.ts中注册
```typescript
// 注册svg-icon
import 'virtual:svg-icons-register'
```
4. 在src/icons/svg目录中添加svg图标
5. 在组件中使用
```vue
<template>
<svg-icon name="user"/>
</template>
<script setup>
import { SvgIcon } from 'vue3-common'
</script>
```