Vue3 + Electron 知识点总结 · 2026-03-21
我是一名对代码狂热的 IT 工作者,目前在一家公司任职前后端开发工程师。以后每天都会更新 CSDN 和稀土掘金的文章——工作中写了什么代码都会在平台上展示 🚀 不会公布公司秘密,只是从中提取 IT 语言的知识点,提供给大家学习使用。
我也希望自己一直不忘初心,帖子为证,只要我一天在工位上,都会定时给大家分享开发收获和经验!!! 💪
本次共识别 24 个知识点,覆盖 13 个分类。
📂 提取来源(共 26 个文件,点击展开)
| 文件路径 | 识别到的知识点 |
|---|---|
electron/main/index.ts | ipcMain.handle(), contextIsolation, BrowserWindow, webContents, app 生命周期, session(会话管理), dialog(系统对话框) |
src/components/zzb_empty/index.vue | script setup, defineProps(), v-if / v-else, v-slot / #slot |
src/components/zzb_layout_head/index.vue | script setup, v-slot / #slot, ref(), onMounted() |
src/components/zzb_member_info/index.vue | script setup, defineProps(), v-slot / #slot, v-show, computed(), useRouter() |
src/components/zzb_member_login_modal/index.vue | script setup, defineExpose(), v-slot / #slot, v-model, ref(), computed(), useRouter() |
src/components/zzb_spin/index.vue | script setup, defineProps(), v-if / v-else, v-slot / #slot |
src/components/zzb_table/index.vue | v-if / v-else, v-slot / #slot, v-model, v-for |
src/components/zzb_table/scroll_list.vue | v-if / v-else, v-slot / #slot, v-for, nextTick() |
src/layout/vab-menu/index.vue | script setup, v-slot / #slot, v-show, v-model, v-for, ref(), computed(), useRouter(), useRoute(), watch() |
src/views/commentAnalysis/components/left_content/index.vue | script setup, defineProps(), defineEmits(), v-for, ref(), computed() |
src/views/commentAnalysis/components/right_content/index.vue | script setup, v-for |
src/views/commentAnalysis/index.vue | dialog(系统对话框), script setup, v-slot / #slot, v-model, ref(), computed() |
src/views/generateResult/components/period_detail/index.vue | script setup, v-if / v-else, v-slot / #slot, v-model, v-for, ref(), computed() |
src/views/generateResult/components/period_detail/pick_list_modal/index.vue | script setup, defineProps(), defineEmits(), v-if / v-else, v-for, ref(), computed(), watch() |
src/views/generateResult/components/period_overall/index.vue | script setup |
src/views/generateResult/components/period_overall/overall_section/index.vue | script setup, v-slot / #slot, v-model, v-for, ref(), computed(), onMounted(), onUnmounted(), nextTick() |
src/views/generateResult/components/period_overall/sku_section/index.vue | script setup, v-slot / #slot, ref(), onMounted(), onUnmounted(), nextTick(), watch() |
src/views/generateResult/components/period_overall/trend_section/index.vue | script setup, v-slot / #slot, ref(), onMounted(), onUnmounted(), nextTick(), watch() |
src/views/generateResult/index.vue | script setup |
src/views/home/components/handler_bar/index.vue | script setup, v-if / v-else, v-slot / #slot, ref(), computed() |
src/views/home/index.vue | script setup |
src/views/member/components/account_info/index.vue | script setup, v-if / v-else, v-slot / #slot, v-for, computed() |
src/views/member/components/credits_info/index.vue | script setup, v-slot / #slot |
src/views/member/components/left_content/index.vue | script setup, defineProps(), defineEmits(), v-slot / #slot, v-for |
src/views/member/components/recharge_orders/index.vue | script setup, v-slot / #slot, v-model, ref(), onMounted() |
src/views/member/index.vue | script setup, v-slot / #slot, v-model, ref(), computed() |
目录
- 🔗 Electron 进程通信
- 🔒 Electron 安全
- 🪟 Electron 窗口
- ⚙️ Electron 应用
- 🖥️ Electron 系统集成
- 🚀 Vue3 基础
- 📡 组件通信
- 🎯 模板指令
- ⚡ 响应式 API
- 🔌 生命周期
- 🗺️ Vue Router
- 🔧 工具函数
- 👁️ 侦听器
🔗 Electron 进程通信
ipcMain.handle()
项目中的用法:
📂 来自 1 个文件(点击展开)
electron/main/index.ts
}
// ← 重点:ipcMain.handle
ipcMain.handle("getMainWindowInfo", async (event, args) => {
return {
winId: mainWindow.id,
winBounds: mainWindow.getBounds(),
isMaximized: mainWindow.isMaximized(),
isMinimized: mainWindow.isMinimized(),
isFullScreen: mainWindow.isFullScreen(),
isFocused: mainWindow.isFocused(),
是什么: 主进程注册 IPC 处理器,响应渲染进程的 invoke 调用,是主进程接收消息的核心方式。
面试 Q&A:
❓ Electron 进程间通信有哪几种方式?
💡 ① ipcMain.handle + ipcRenderer.invoke(推荐,Promise 形式,有返回值)② ipcMain.on + ipcRenderer.send(单向,无返回值)③ webContents.send + ipcRenderer.on(主进程推送到渲染进程)。
❓ ipcMain.handle 和 ipcMain.on 的区别?
💡 handle 用于双向通信,对应 invoke,支持 async/await 返回值;on 用于单向通信,对应 send,无返回值。现代开发推荐全部用 handle/invoke,语义更清晰。
🔒 Electron 安全
contextIsolation
项目中的用法:
📂 来自 1 个文件(点击展开)
electron/main/index.ts
// disableDevToolsSecurityWarnings: true,
nodeIntegration: true, // 禁用渲染进程中的 Node.js 集成
// ← 重点:contextIsolation
contextIsolation: true, // 启用上下文隔离 contextBridge 是 Electron 提供的一个用于在预加载脚本和渲染进程之间安全通信的 API,它要求 contextIsolation 必须开启才能正常使用
},
icon: IconPath
})
// 设置全局主窗口引用
global.mainWindow = mainWindow;
process.env.VITE_DEV_SERVER_URL ? mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL || '') : mainWindow.loadURL(HomeIndexUrl)
是什么: 启用上下文隔离,渲染进程和 preload 运行在独立 JS 环境,必须配合 contextBridge 通信。
面试 Q&A:
❓ contextIsolation 和 nodeIntegration 如何配合?
💡 推荐配置:`contextIsolation: true, nodeIntegration: false`(默认值)。preload 脚本在隔离环境中可访问 Node.js,通过 contextBridge 只暴露安全 API 给渲染进程,这是 Electron 官方推荐的安全模型。
🪟 Electron 窗口
BrowserWindow
项目中的用法:
📂 来自 1 个文件(点击展开)
electron/main/index.ts
// ← 重点:BrowserWindow
import { app, BrowserWindow, ipcMain, session, screen, Menu, dialog, Tray } from 'electron'
import path from 'node:path'
import './work-server/index';
import { IconPath, isWin, AppTitle, HomePreload, HomeIndexUrl } from './config'
import main_tray from './main_tray';
import { AddDevShortcuts } from './utils';
app.commandLine.appendSwitch('ignore-certificate-errors', 'true');
是什么: 创建并控制原生浏览器窗口,是 Electron 应用的核心 UI 容器。
面试 Q&A:
❓ BrowserWindow 常用配置有哪些?
💡 `frame:false`(无边框窗口)、`resizable`(是否可调整大小)、`webPreferences.preload`(预加载脚本路径)、`webPreferences.contextIsolation`(上下文隔离)、`icon`(窗口图标)、`transparent`(透明窗口)。
❓ 如何实现无边框窗口的拖拽?
💡 设置 `frame:false` 后标题栏消失,拖拽区域需在 CSS 中设置 `-webkit-app-region: drag`,按钮等可点击区域设置 `-webkit-app-region: no-drag`。
webContents
项目中的用法:
📂 来自 1 个文件(点击展开)
electron/main/index.ts
main_tray.CreateTray(mainWindow)
AddDevShortcuts(mainWindow)
// mainWindow.webContents.toggleDevTools();
}
ipcMain.handle("getMainWindowInfo", async (event, args) => {
return {
winId: mainWindow.id,
winBounds: mainWindow.getBounds(),
isMaximized: mainWindow.isMaximized(),
是什么: BrowserWindow 的核心属性,控制网页内容,可执行 JS、监听网络请求、注入脚本等。
面试 Q&A:
❓ webContents 常用的操作有哪些?
💡 `webContents.send()`(向渲染进程发消息)、`webContents.executeJavaScript()`(在页面执行 JS)、`webContents.openDevTools()`(打开开发者工具)、`webContents.on('did-finish-load', cb)`(页面加载完成)。
⚙️ Electron 应用
app 生命周期
📂 来自 1 个文件(点击展开)
electron/main/index.ts
是什么: Electron app 模块控制应用生命周期,管理启动、窗口关闭、退出等核心事件。
面试 Q&A:
❓ Electron 应用的启动流程是什么?
💡 ① app.whenReady() 等待应用就绪 ② 创建 BrowserWindow ③ 加载页面(loadURL/loadFile)④ 监听 window-all-closed 事件处理退出(macOS 需特殊处理不退出进程)。
❓ app.whenReady 和 app.on('ready') 的区别?
💡 功能相同,都在应用初始化完成后触发。whenReady 返回 Promise,写法更现代;on('ready') 是回调风格。推荐用 whenReady().then(() => createWindow())。
🖥️ Electron 系统集成
session(会话管理)
📂 来自 1 个文件(点击展开)
electron/main/index.ts
是什么: 管理浏览器会话,可设置代理、拦截请求、管理 Cookie、设置分区隔离多标签页。
面试 Q&A:
❓ Electron 中 partition 是什么?
💡 partition 是会话隔离机制,`partition: 'persist:name'` 创建持久化会话(Cookie/缓存保存到磁盘);`partition: 'name'` 创建临时会话(关闭后清除)。项目中用于隔离不同账号的登录状态。
dialog(系统对话框)
📂 来自 2 个文件(点击展开)
electron/main/index.tssrc/views/commentAnalysis/index.vue
是什么: 调用系统原生对话框:文件选择、保存文件、消息提示框等。
面试 Q&A:
❓ dialog 必须在主进程调用吗?
💡 是的,dialog 属于主进程模块。渲染进程需要通过 ipcRenderer.invoke 调用主进程,由主进程调用 dialog 后返回结果。Electron 28+ 的 dialog.showOpenDialog 可直接传 BrowserWindow 实例。
🚀 Vue3 基础
script setup
项目中的用法:
📂 来自 23 个文件(点击展开)
src/components/zzb_empty/index.vuesrc/components/zzb_layout_head/index.vuesrc/components/zzb_member_info/index.vuesrc/components/zzb_member_login_modal/index.vuesrc/components/zzb_spin/index.vuesrc/layout/vab-menu/index.vuesrc/views/commentAnalysis/components/left_content/index.vuesrc/views/commentAnalysis/components/right_content/index.vuesrc/views/commentAnalysis/index.vuesrc/views/generateResult/components/period_detail/index.vuesrc/views/generateResult/components/period_detail/pick_list_modal/index.vuesrc/views/generateResult/components/period_overall/index.vuesrc/views/generateResult/components/period_overall/overall_section/index.vuesrc/views/generateResult/components/period_overall/sku_section/index.vuesrc/views/generateResult/components/period_overall/trend_section/index.vuesrc/views/generateResult/index.vuesrc/views/home/components/handler_bar/index.vuesrc/views/home/index.vuesrc/views/member/components/account_info/index.vuesrc/views/member/components/credits_info/index.vuesrc/views/member/components/left_content/index.vuesrc/views/member/components/recharge_orders/index.vuesrc/views/member/index.vue
</div>
</template>
// ← 重点:script setup
<script setup>
defineProps({
emptyTip: { type: String, default: '暂无数据' },
emptyTipMsg: { type: String, default: '' },
});
</script>
<style lang="less" scoped>
.zzbEmptyBox {
是什么: 组合式 API 语法糖,顶层变量/函数自动暴露给模板,无需 return。
面试 Q&A:
❓ Vue2 Options API 和 Vue3 Composition API 有什么区别?
💡 Options API 按选项类型组织代码(data/methods/computed),逻辑分散;Composition API 按功能聚合,逻辑复用靠 composable 函数,更适合大型项目。
❓ setup() 和 `<script setup>` 有什么不同?
💡 setup() 是函数,需要手动 return 暴露给模板;`<script setup>` 是编译器语法糖,顶层声明自动暴露,代码更简洁,性能略优(编译阶段优化)。
📡 组件通信
defineProps()
项目中的用法:
📂 来自 6 个文件(点击展开)
src/components/zzb_empty/index.vuesrc/components/zzb_member_info/index.vuesrc/components/zzb_spin/index.vuesrc/views/commentAnalysis/components/left_content/index.vuesrc/views/generateResult/components/period_detail/pick_list_modal/index.vuesrc/views/member/components/left_content/index.vue
</template>
<script setup>
// ← 重点:defineProps
defineProps({
emptyTip: { type: String, default: '暂无数据' },
emptyTipMsg: { type: String, default: '' },
});
</script>
<style lang="less" scoped>
.zzbEmptyBox {
width: 100%;
是什么: 声明组件接收的 props,父传子的核心方式,支持类型约束和默认值。
面试 Q&A:
❓ Vue3 有哪些组件通信方式?
💡 ① props/emit(父子)② v-model(双向)③ ref + defineExpose(父调子方法)④ provide/inject(跨层级)⑤ Pinia(全局状态)⑥ mitt 事件总线(任意组件)
❓ props 是单向数据流,子组件能直接修改吗?
💡 不能直接修改,会报警告。正确做法:① emit 通知父组件修改 ② 将 prop 赋值给本地 ref 再修改本地数据 ③ 使用 v-model。
defineExpose()
项目中的用法:
📂 来自 1 个文件(点击展开)
src/components/zzb_member_login_modal/index.vue
// ← 重点:defineExpose
defineExpose({ updataModalState })
</script>
<style lang="less" >
.zzb_member_login_modal {
.ant-modal-content {
padding: 0px;
是什么: 显式暴露组件内部属性/方法,才能被父组件通过 ref 访问。
面试 Q&A:
❓ 为什么 script setup 组件用 ref 拿不到属性?
💡 script setup 默认是封闭的(closed),不像 Options API 组件自动暴露所有实例属性。必须用 defineExpose({ method, data }) 显式声明要暴露的内容。
defineEmits()
项目中的用法:
📂 来自 3 个文件(点击展开)
src/views/commentAnalysis/components/left_content/index.vuesrc/views/generateResult/components/period_detail/pick_list_modal/index.vuesrc/views/member/components/left_content/index.vue
});
// ← 重点:defineEmits
const emit = defineEmits(["update:modelValue"]);
const activeId = computed({
get: () => props.modelValue,
set: (v) => emit("update:modelValue", v),
});
</script>
是什么: 声明组件可触发的自定义事件,子传父的核心方式。
面试 Q&A:
❓ emit 和 v-model 的关系?
💡 v-model 本质是 `:modelValue + @update:modelValue` 的语法糖。子组件用 defineEmits(['update:modelValue']),然后 emit('update:modelValue', newVal) 即可实现双向绑定。
🎯 模板指令
v-if / v-else
📂 来自 8 个文件(点击展开)
src/components/zzb_empty/index.vuesrc/components/zzb_spin/index.vuesrc/components/zzb_table/index.vuesrc/components/zzb_table/scroll_list.vuesrc/views/generateResult/components/period_detail/index.vuesrc/views/generateResult/components/period_detail/pick_list_modal/index.vuesrc/views/home/components/handler_bar/index.vuesrc/views/member/components/account_info/index.vue
是什么: 条件渲染,false 时节点从 DOM 移除并销毁组件,适合不频繁切换的场景。
面试 Q&A:
❓ v-if 和 v-show 如何选择?
💡 频繁切换用 v-show(只改 display,开销小);初始条件为假且不常切换用 v-if(减少初始渲染开销)。v-if 切换时组件会销毁重建,触发完整生命周期。
❓ v-if 和 v-for 的优先级?
💡 Vue3 中 v-if 优先级高于 v-for(Vue2 相反)。不建议同时使用,应用 template 包裹 v-for,在内部元素上用 v-if,或用 computed 过滤数据。
v-slot / #slot
📂 来自 19 个文件(点击展开)
src/components/zzb_empty/index.vuesrc/components/zzb_layout_head/index.vuesrc/components/zzb_member_info/index.vuesrc/components/zzb_member_login_modal/index.vuesrc/components/zzb_spin/index.vuesrc/components/zzb_table/index.vuesrc/components/zzb_table/scroll_list.vuesrc/layout/vab-menu/index.vuesrc/views/commentAnalysis/index.vuesrc/views/generateResult/components/period_detail/index.vuesrc/views/generateResult/components/period_overall/overall_section/index.vuesrc/views/generateResult/components/period_overall/sku_section/index.vuesrc/views/generateResult/components/period_overall/trend_section/index.vuesrc/views/home/components/handler_bar/index.vuesrc/views/member/components/account_info/index.vuesrc/views/member/components/credits_info/index.vuesrc/views/member/components/left_content/index.vuesrc/views/member/components/recharge_orders/index.vuesrc/views/member/index.vue
是什么: 插槽内容分发,支持默认插槽、具名插槽、作用域插槽。
面试 Q&A:
❓ 默认、具名、作用域插槽的区别?
💡 默认插槽:`<slot>` 接收未命名内容;具名插槽:`<slot name='header'>` + `<template #header>` 指定位置;作用域插槽:`<slot :data='item'>` + `<template #default='{ data }'>` 让父组件拿到子组件数据。
v-show
项目中的用法:
📂 来自 2 个文件(点击展开)
src/components/zzb_member_info/index.vuesrc/layout/vab-menu/index.vue
<div class="member_info" :class="{ collapsed }" @click="toMember">
<img class="avatar" :src="memberLogo">
// ← 重点:v-show
<div class="text_group" v-show="!collapsed">
<div class="username">{{ memberName }}</div>
<div class="stats">
<span class="stats_label">算力:</span>
<span class="stats_value">{{ memberCredit }}</span>
</div>
</div>
</div>
是什么: 条件显示,false 时设 display:none,节点始终存在 DOM,切换开销低。
面试 Q&A:
❓ v-show 能用在 `<template>` 标签上吗?
💡 不能。template 是虚拟容器,不渲染为真实 DOM 元素,v-show 需要操作元素的 style,所以无法作用于 template。v-if 可以用在 template 上。
v-model
项目中的用法:
📂 来自 8 个文件(点击展开)
src/components/zzb_member_login_modal/index.vuesrc/components/zzb_table/index.vuesrc/layout/vab-menu/index.vuesrc/views/commentAnalysis/index.vuesrc/views/generateResult/components/period_detail/index.vuesrc/views/generateResult/components/period_overall/overall_section/index.vuesrc/views/member/components/recharge_orders/index.vuesrc/views/member/index.vue
<template>
// ← 重点:v-model
<a-modal wrapClassName="zzb_member_login_modal" v-model:open="open" :title="null" width="714px" :footer="null" :centered="true" :closable="false">
<div class="content">
<div class="left_box">
<img src="https://feiyingai.com/fy_tool_app/images/fy_login_img.svg" alt="">
</div>
<div class="right_box" @keydown.enter.prevent="handleLogin">
<i class="window-control icon-close" @click="closeWindow"></i>
// ← 重点:v-model
<Tabs v-model:active-tab="activeTab" />
是什么: 双向绑定语法糖,等价于 :modelValue + @update:modelValue。
面试 Q&A:
❓ v-model 的实现原理?
💡 编译器将 v-model 展开为 :modelValue='val' 和 @update:modelValue='val=$event'。组件内用 defineProps(['modelValue']) + defineEmits(['update:modelValue']) 配合使用。
❓ Vue3 和 Vue2 的 v-model 有什么变化?
💡 Vue2 用 :value + @input,修改靠 .sync;Vue3 统一为 :modelValue + @update:modelValue,支持多个 v-model(v-model:title、v-model:content),去掉了 .sync。
v-for
项目中的用法:
📂 来自 10 个文件(点击展开)
src/components/zzb_table/index.vuesrc/components/zzb_table/scroll_list.vuesrc/layout/vab-menu/index.vuesrc/views/commentAnalysis/components/left_content/index.vuesrc/views/commentAnalysis/components/right_content/index.vuesrc/views/generateResult/components/period_detail/index.vuesrc/views/generateResult/components/period_detail/pick_list_modal/index.vuesrc/views/generateResult/components/period_overall/overall_section/index.vuesrc/views/member/components/account_info/index.vuesrc/views/member/components/left_content/index.vue
<div class="zzb_table_header_box" ref="zzb_table_header_box">
<div class="zzb_tr zzb_table_header" ref="header">
// ← 重点:v-for
<template v-for="(item, index) of headerColumns" :key="index">
<div class="zzb_tr_item" :class="[
...getTrClass(index),
leftFixedInd === index && isHeaderScroll ? (isLeftScroll ? 'leftBorderScroll' : 'leftBorder') : '',
rightFixedInd == index && isHeaderScroll ? (isRightScrollBottom ? 'rightBorder' : 'rightBorderScroll') : '',
checkboxTips && item.type == 'checkbox' ? 'zzb_checkbox_div' : '',
item.sorter ? 'headerTrCursor' : '',
]" :style="{ ...getTrStyle(index, 'hea') }" :disabled="sortDisable"
是什么: 列表渲染,必须加 :key(唯一且稳定),key 用于 Diff 算法复用节点。
面试 Q&A:
❓ 为什么 v-for 必须加 key?key 用 index 有什么问题?
💡 key 让 Diff 算法识别节点身份,复用 DOM 减少操作。用 index 做 key 在列表增删时会导致错误复用(节点身份和数据不匹配),造成状态错乱,应用数据唯一 ID。
❓ Vue3 的 Diff 算法有什么优化?
💡 Vue3 Diff 引入最长递增子序列(LIS)算法,最小化节点移动次数;同时增加静态标记(patchFlag)跳过静态节点对比,比 Vue2 快 1.3~2 倍。
⚡ 响应式 API
ref()
项目中的用法:
📂 来自 13 个文件(点击展开)
src/components/zzb_layout_head/index.vuesrc/components/zzb_member_login_modal/index.vuesrc/layout/vab-menu/index.vuesrc/views/commentAnalysis/components/left_content/index.vuesrc/views/commentAnalysis/index.vuesrc/views/generateResult/components/period_detail/index.vuesrc/views/generateResult/components/period_detail/pick_list_modal/index.vuesrc/views/generateResult/components/period_overall/overall_section/index.vuesrc/views/generateResult/components/period_overall/sku_section/index.vuesrc/views/generateResult/components/period_overall/trend_section/index.vuesrc/views/home/components/handler_bar/index.vuesrc/views/member/components/recharge_orders/index.vuesrc/views/member/index.vue
<script setup>
// 响应式数据
// ← 重点:ref
const isMaximized = ref(false);
// 最小化窗口
const minimizeWindow = async () => {
const winInfo = await PU.GetMainWindowInfo();
PU.MinimizeWindow(winInfo.winId);
};
是什么: 将基本类型包装为响应式对象,JS 中访问要加 .value,模板中自动解包。
面试 Q&A:
❓ ref 和 reactive 的区别?
💡 ref 用于基本类型(内部用 RefImpl 包装),访问需 .value;reactive 用于对象/数组(基于 Proxy),直接访问属性。ref 也可包对象,内部会自动调用 reactive。
❓ 模板里为什么不用写 .value?
💡 模板编译时会自动对 ref 解包,访问 ref.value 的操作由编译器插入,开发者无感知。
computed()
项目中的用法:
📂 来自 11 个文件(点击展开)
src/components/zzb_member_info/index.vuesrc/components/zzb_member_login_modal/index.vuesrc/layout/vab-menu/index.vuesrc/views/commentAnalysis/components/left_content/index.vuesrc/views/commentAnalysis/index.vuesrc/views/generateResult/components/period_detail/index.vuesrc/views/generateResult/components/period_detail/pick_list_modal/index.vuesrc/views/generateResult/components/period_overall/overall_section/index.vuesrc/views/home/components/handler_bar/index.vuesrc/views/member/components/account_info/index.vuesrc/views/member/index.vue
const userStore = useUserStore();
// ← 重点:computed
const memberLogo = computed(() => userStore.memberLogo);
// ← 重点:computed
const memberName = computed(() => userStore.NickName || userStore.UserName || "-");
// ← 重点:computed
const memberCredit = computed(() => userStore.Credit ?? 0);
const toMember = () => {
router.push('/member')
}
</script>
是什么: 基于响应式数据派生的缓存值,依赖不变时不重新计算。
面试 Q&A:
❓ computed 和 methods 的区别?
💡 computed 有缓存,依赖不变则直接返回缓存值,适合计算量大的场景;methods 每次调用都重新执行,适合需要触发副作用的操作。
❓ 如何实现可写的 computed?
💡 传入包含 get 和 set 的对象:`computed({ get: () => x.value, set: v => x.value = v })`,set 中手动更新源数据。
🔌 生命周期
onMounted()
项目中的用法:
📂 来自 5 个文件(点击展开)
src/components/zzb_layout_head/index.vuesrc/views/generateResult/components/period_overall/overall_section/index.vuesrc/views/generateResult/components/period_overall/sku_section/index.vuesrc/views/generateResult/components/period_overall/trend_section/index.vuesrc/views/member/components/recharge_orders/index.vue
// 生命周期钩子
// ← 重点:onMounted
onMounted(() => {
checkMaximizeState();
});
</script>
<style lang="less" scoped>
是什么: 组件挂载到真实 DOM 后执行,可安全操作 DOM、发起请求。
面试 Q&A:
❓ Vue3 生命周期和 Vue2 的对应关系?
💡 beforeCreate/created → setup();beforeMount → onBeforeMount;mounted → onMounted;beforeUpdate → onBeforeUpdate;updated → onUpdated;beforeDestroy → onBeforeUnmount;destroyed → onUnmounted。
❓ 为什么不在 setup 顶层直接操作 DOM?
💡 setup 执行时组件还未挂载,DOM 不存在。需要在 onMounted 里操作 DOM,此时模板已渲染完毕。
onUnmounted()
项目中的用法:
📂 来自 3 个文件(点击展开)
src/views/generateResult/components/period_overall/overall_section/index.vuesrc/views/generateResult/components/period_overall/sku_section/index.vuesrc/views/generateResult/components/period_overall/trend_section/index.vue
})
// ← 重点:onUnmounted
onUnmounted(() => {
window.removeEventListener("resize", resizeChart)
chart?.dispose()
chart = null
})
</script>
<style lang="less" src="./index.less" scoped></style>
是什么: 组件销毁后执行,必须在此清除定时器、取消订阅,防止内存泄漏。
面试 Q&A:
❓ 不清除定时器会发生什么?
💡 组件销毁后定时器仍在运行,回调中访问已销毁组件的数据会报错,且定时器无法被 GC 回收,导致内存泄漏。每次重新挂载组件还会叠加创建新定时器。
🗺️ Vue Router
useRouter()
项目中的用法:
📂 来自 3 个文件(点击展开)
src/components/zzb_member_info/index.vuesrc/components/zzb_member_login_modal/index.vuesrc/layout/vab-menu/index.vue
<script setup>
import { useUserStore } from "@/store/member_store";
// ← 重点:useRouter
import { useRouter } from 'vue-router'
// ← 重点:useRouter
const router = useRouter()
defineProps({
collapsed: { type: Boolean, default: false },
});
是什么: 获取路由实例,用于编程式导航 push/replace/go/back。
面试 Q&A:
❓ push 和 replace 的区别?
💡 push 往历史记录栈推入新记录,可以后退;replace 替换当前记录,不增加历史栈,无法后退到替换前的页面。登录后跳转首页通常用 replace,避免用户后退回登录页。
❓ 导航守卫如何做权限控制?
💡 `router.beforeEach((to, from, next) => { if (to.meta.requiresAuth && !isLogin) next('/login'); else next() })`,在路由 meta 中标记需要认证的页面,守卫中统一拦截。
useRoute()
项目中的用法:
📂 来自 1 个文件(点击展开)
src/layout/vab-menu/index.vue
<script setup>
import MenuItem from "./components/menu_item/index.vue";
// ← 重点:useRoute
import { useRoute, useRouter } from "vue-router";
import { useBaesConfigStore } from "@/store/base_store";
// ← 重点:useRoute
const route = useRoute();
// ← 重点:useRoute
const router = useRouter();
const routerList = ref([]);
const defaultRouter = ref([]);
是什么: 获取当前路由对象:params、query、path、meta、name 等。
面试 Q&A:
❓ params 和 query 的区别?
💡 params 是路径参数(/user/:id),必须在路由定义中声明,刷新后仍存在(history 模式);query 是查询字符串(?id=1),无需声明,URL 中可见,刷新后仍存在。
❓ 路由参数变化但组件不更新怎么解决?
💡 同一组件复用时不会重新挂载。解决:① watch(() => route.params, handler) ② 给 router-view 加 :key='route.fullPath' 强制重建。
🔧 工具函数
nextTick()
项目中的用法:
📂 来自 4 个文件(点击展开)
src/components/zzb_table/scroll_list.vuesrc/views/generateResult/components/period_overall/overall_section/index.vuesrc/views/generateResult/components/period_overall/sku_section/index.vuesrc/views/generateResult/components/period_overall/trend_section/index.vue
//获取列表项的当前尺寸
updateItemsSize() {
// ← 重点:nextTick
this.$nextTick(function () {
let nodes = this.$refs.content.children;
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i];
if (node.dataset.name != "zzbItem") return;
let rect = node.getBoundingClientRect();
let height = rect.height;
let index = +node.id.slice(1);
是什么: 等待下一次 DOM 更新后执行,用于数据修改后立即操作更新后的 DOM。
面试 Q&A:
❓ nextTick 的原理?
💡 Vue 的 DOM 更新是异步批量的(微任务队列)。修改数据后 DOM 不会立即更新,nextTick 将回调推入同一微任务队列的末尾,等 DOM 更新完成后执行。底层用 Promise.then 实现。
❓ nextTick 和 setTimeout 的区别?
💡 nextTick 用微任务(Promise),在当前宏任务结束、下一个宏任务开始前执行,时机更早;setTimeout 是宏任务,会在所有微任务执行完后才运行,时机更晚,可能看到中间状态。
👁️ 侦听器
watch()
项目中的用法:
📂 来自 4 个文件(点击展开)
src/layout/vab-menu/index.vuesrc/views/generateResult/components/period_detail/pick_list_modal/index.vuesrc/views/generateResult/components/period_overall/sku_section/index.vuesrc/views/generateResult/components/period_overall/trend_section/index.vue
const toHome = () => { router.push('/home'); }
// ← 重点:watch
watch(
() => route.path,
updateRouteState,
{ immediate: true }
);
</script>
<style lang="less" scoped>
是什么: 侦听指定数据源,变化时执行回调,可获取新旧值,支持 deep/immediate。
面试 Q&A:
❓ watch 和 watchEffect 的核心区别?
💡 watch 需要明确指定侦听源,懒执行(默认不立即执行),可拿到新旧值;watchEffect 自动收集依赖,立即执行一次,拿不到旧值。
❓ watch 监听对象的某个属性怎么写?
💡 用 getter 函数:`watch(() => obj.key, callback)`。直接写 `watch(obj.key, ...)` 只是监听当时的值,不是响应式的。
❓ 如何停止侦听?
💡 `const stop = watch(...); stop()` 调用返回值即可停止。组件卸载时会自动停止,手动停止用于在卸载前提前结束侦听。