ES6+ 模块化 import 大括号规则(极简版)
这是前端 / TS 最基础、最常用的知识点,只看导出方式,和导入没关系,一句话总结:
核心规则
- 导入默认导出 → 不加
{} - 导入命名导出 → 必须加
{}
1. 不加 {}:导入【默认导出】
导出方(别人写的)
js
// 一个文件只能有 1 个 default 导出
export default 函数/变量/对象
导入方(你写的)
ts
// 名字可以随便起,不需要和导出一致
import 自定义名字 from 'xxx'
例子:
ts
// 导出文件 utils.js
const getName = () => '小明'
export default getName // 默认导出
// 你的文件
import myName from './utils' // ✅ 不加 {}
import abc from './utils' // ✅ 也可以,名字随便写
2. 加 {}:导入【命名导出】
导出方
js
// 一个文件可以有 N 个命名导出
export const 变量1 = xxx
export function 函数1() {}
导入方
ts
// 名字必须和导出的一模一样,不能乱改
import { 导出的名字 } from 'xxx'
例子:
ts
// 导出文件 utils.js
export const getName = () => '小明' // 命名导出
export const age = 18
// 你的文件
import { getName } from './utils' // ✅ 必须加 {}
import { age } from './utils' // ✅ 必须加 {}
快速记忆口诀
默认导出不带花,命名导出必须花
总结
- 看到
export default→ 导入不加{} - 看到
export 变量/函数→ 导入必须加{} - 一个文件可以同时有默认导出 + 多个命名导出
async /await 到底是什么?
一句话:让异步代码(需要等待的代码)写起来像同步代码一样舒服、好读、好控制顺序。
async:放在函数前面 → 告诉 JS "这是一个异步函数"await:放在异步操作前面 → 告诉 JS"等我做完,再往下走"
核心用法(背会这 2 条就够)
- await 必须写在 async 函数里
- await 后面跟一个 "需要等待" 的操作(Promise / 异步请求 / 路由加载 / 定时器等)
你问的场景:Vue 等待路由加载完再挂载页面
这是 Vue3 + TS 最标准、最常用的写法:
ts
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
async function start() {
// 1. 创建应用
const app = createApp(App)
// 2. 等待路由准备完成(关键!)
await router.isReady()
// 3. 路由准备好了,再挂载页面
app.mount('#app')
}
// 启动
start()
它到底做了什么?
- 不等待:页面直接挂载 → 路由还没加载好 → 页面闪、报错、跳转异常
- 加 await 等待 :路由加载完成 → 再挂载页面 → 流程稳定、不报错
最通俗的比喻
async= 我要做一件需要等的事await= 等一下!先别往下跑!
就像:
ts
async function 吃饭() {
await 做饭() // 必须等饭做好
再吃() // 饭好了才执行
}
通用写法模板(任何地方都能用)
ts
// 1. 函数前面加 async
async function 函数名() {
// 2. 异步操作前面加 await
await 异步操作()
// 3. 等待完成后,才会执行这里
console.log('等待完成!继续执行')
}
超简记忆口诀
async 包函数,await 等异步,顺序不乱跑,代码更安全。
总结
- async 写在函数前 → 开启异步函数
- await 写在异步操作前 → 等待完成再往下
- Vue 中必须写 await router.isReady () → 保证路由加载完再挂载页面,避免报错
为什么必须等路由加载完,再挂载页面? 不等会出什么问题?
一句话核心原因
路由没准备好,页面就先渲染 = 一定会出跳转错误、刷新白屏、导航守卫不生效。
1. 最常见的崩溃场景(你一定遇到过)
你刷新页面时:
- 浏览器一刷新 → 页面瞬间挂载
- 路由还在初始化(没准备好)
- 页面一上来就要跳转 / 读取当前路由
- 直接报错:导航守卫未执行、跳转失败、白屏
典型报错:
plaintext
Uncaught (in promise) Error: Navigation cancelled from "/" to "/home"
或者
plaintext
Cannot read property 'push' of undefined
2. 路由没准备好时,它 "缺" 什么?
router.isReady() 等待的是这 3 件事:
- 所有路由规则加载完成
- 初始导航已经执行完毕(第一次进入页面的跳转完成)
- 全局守卫(beforeEach)全部执行完
这三件事没做完,路由就是 "残废" 的。
3. 不等的后果(真实项目必踩坑)
① 刷新页面白屏 / 跳转不生效
用户一刷新 → 页面渲染了,但路由还没准备好 → 不知道当前应该显示哪个页面
② 全局守卫(权限判断)不执行
比如你写了登录验证:
ts
router.beforeEach((to, from, next) => {
// 权限判断
})
不等路由准备好,守卫根本不执行!→ 直接导致权限失效、页面越权访问。
③ 报错:Navigation cancelled
最经典的路由报错,100% 是因为没等路由 ready 就挂载页面。
④ 服务端渲染 / 预渲染失效
SSR / SSG 项目,不等路由直接渲染会直接崩溃。
4. 等了之后有什么好处?
- 路由初始化完成
- 第一次跳转完成
- 守卫全部执行完
- 页面知道自己该显示什么内容
然后再挂载页面 → 绝对稳定、不报错、不白屏、不乱跳。
5. 最通俗的比喻
路由 = 地图 页面 = 汽车
- 不等地图加载完 → 汽车就开出去 → 迷路、撞墙、抛锚
- 等地图加载完 → 再开车 → 路线清晰、安全到达
就是这个道理。
6. 最终结论(必须记住)
只要你用 Vue Router,main.ts 必须这样写:
ts
async function start() {
const app = createApp(App)
app.use(router)
// 必须等!
await router.isReady()
// 等完再挂载
app.mount('#app')
}
start()
为什么?
因为:路由没准备好 → 页面渲染就是错的。
超级总结
- 不等路由 ready → 刷新白屏、跳转报错、权限失效
- 等路由 ready → 保证第一次导航完成、守卫执行、页面稳定
- 这是 Vue 官方标准规范,所有项目都必须写
为什么明明可以直接写代码,非要包一层 function 函数? 尤其是你看到的 async function appInit()。
一句话核心答案
因为 await 只能在 async 函数里用,不能直接写在最外层! 封装函数 = 给 await 找一个 "家"。
1. 最关键的原因:语法强制要求
你想写:
ts
// main.ts 最顶层
await router.isReady() // ❌ 报错!
app.mount('#app')
直接报错: Syntax error: await is only valid in async function翻译:await 只能在 async 函数里使用,不能裸奔!
所以你必须包一个函数 ,才能用 await:
ts
async function appInit() {
await router.isReady() // ✅ 正常
app.mount('#app')
}
appInit() // 最后调用一下
这是JS/TS 语法规定,没得选。
2. 第二个原因:代码更干净(启动流程可控)
不封装函数,一堆代码堆在顶部,乱糟糟:
ts
// 不封装 ❌
const app = createApp(App)
app.use(router)
app.use(pinia)
await router.isReady() // 报错
app.mount('#app')
封装函数后,结构清晰、一目了然:
ts
// 封装 ✅
async function appInit() {
const app = createApp(App)
app.use(router)
app.use(pinia)
await router.isReady()
app.mount('#app')
}
appInit()
好处:
- 所有启动逻辑都在一个函数里
- 想改启动流程,只改这一个地方
- 别人一看就知道:这是项目初始化
3. 第三个原因:方便控制 "什么时候启动"
封装成函数,你可以:
- 延迟启动
- 条件启动
- 出错重试
- 加捕获异常
比如:
ts
// 等页面加载完再启动
window.addEventListener('load', () => {
appInit()
})
如果不封装,做不到这么灵活。
4. 第四个原因:方便捕获错误
封装函数后,可以轻松加 try/catch:
ts
async function appInit() {
try {
await router.isReady()
app.mount('#app')
} catch (err) {
// 路由加载失败,给用户提示
alert('页面加载失败,请刷新重试')
console.error(err)
}
}
不封装函数,很难优雅处理异步错误。
最通俗的比喻
- 代码 = 货物
- 函数 = 箱子
- await = 易碎品(必须放箱子里)
你不能把易碎品扔在地上,必须装进箱子里。function 就是装 await 这个易碎品的箱子。
超级总结(背会这 3 点)
- 最主要原因 :
await必须放在async 函数里,不能直接写 - 代码整洁:把启动逻辑打包在一起,清晰不乱
- 可控性强:可延迟、可重试、可捕获错误
所以:不是想封装,是必须封装!
五、const 常量声明(最清晰版)
我给你讲最简单、最实用、面试必问的版本,保证你一看就懂、一用就会。
1. 为什么这里要用 const?
一句话:创建出来的 app 实例,全程不需要重新赋值,所以用 const。
ts
const app = createApp(App)
createApp(App)会创建一个 Vue 应用实例- 这个实例创建后就不会再变了
- 全程只需要这一个 app,不会再写
app = xxx - 所以用 const(常量) 最安全、最规范
2. JS/TS 声明变量的 3 个关键字
只有 3 个:
- var(古老、废弃、别用)
- let(变量:可以改)
- const(常量:不能改)
3. 三者最核心区别(背会这张表)
表格
| 关键字 | 能不能重新赋值 | 作用域 | 推荐使用 |
|---|---|---|---|
| var | 能 | 函数级 | ❌ 不推荐 |
| let | 能 | 块级 {} | ✅ 变量用 |
| const | 不能 | 块级 {} | ✅ 首选 |
4. 最直白解释
① const = 常量(不能被重新赋值)
ts
const name = "小明"
name = "小红" // ❌ 报错!不能改
Vue 里创建的 app、router、store 实例,一生只创建一次,不会变 → 必须用 const。
② let = 变量(可以改)
ts
let age = 18
age = 19 // ✅ 没问题
③ var = 老古董(别用)
- 有变量提升问题
- 容易不小心覆盖变量
- 现在所有项目都不用了
5. 超级重要误区(90% 的人搞错)
const 不是说 "里面内容不能改",而是 "变量本身不能重新赋值"
看例子:
ts
const user = { name: "小明" }
user.name = "小红" // ✅ 可以改!修改对象内容没问题
user = {} // ❌ 报错!重新赋值不行
所以:
- const 声明的对象 / 数组,里面内容可以改
- 但不能把变量整个换成新的
6. 实际项目怎么选?(黄金规则)
记住一句话:
能用 const 就用 const,需要改的时候再换成 let。
- 实例、配置、工具函数 → const
- 计数器、循环变量、会变化的值 → let
- var → 永远不用
超级总结
-
为什么用 const 声明 app? 因为实例创建后不会重新赋值,const 最安全。
-
声明变量的 3 个关键字:
- var(废弃)
- let(变量,可改)
- const(常量,不可重新赋值,首选)
-
规则: 不变 → const 会变 → let var → 别碰
我给你最清晰、最实用、直接能照抄 的 function 函数定义完整语法 ,包含你项目里用到的 async 函数,全部一次讲透。
一、最标准:普通函数(最常用)
ts
// 1. 定义
function 函数名() {
// 代码逻辑
}
// 2. 调用
函数名()
你项目里的样子(不带异步)
ts
function appInit() {
console.log('启动项目')
}
appInit() // 调用
二、带 async 的异步函数(你现在用的)
必须记住:await 只能放在这种函数里
ts
// 1. 定义(前面加 async)
async function 函数名() {
await 异步操作
}
// 2. 调用
函数名()
你项目里真实写法
ts
async function appInit() {
await router.isReady()
app.mount('#app')
}
appInit() // 调用
三、带参数的函数(90% 都会用到)
ts
// 定义
function 函数名(参数1, 参数2) {
return 参数1 + 参数2
}
// 调用
函数名(10, 20)
四、带返回值的函数
ts
function add(a, b) {
return a + b // 返回结果
}
let result = add(1,2) // 接收结果
五、完整语法结构(背这个就够)
ts
[async] function 函数名([参数]) {
// 函数体
[return 返回值]
}
async:可选,要写 await 就必须加function:关键字,必须写函数名:自己起名字():放参数{}:放代码逻辑
六、你项目里的标准启动函数(万能模板)
ts
async function appInit() {
// 1. 创建应用
const app = createApp(App)
// 2. 使用插件
app.use(router)
// 3. 等待路由
await router.isReady()
// 4. 挂载页面
app.mount('#app')
}
// 调用启动函数
appInit()
七、超级记忆口诀
plaintext
function 加名字,
小括号放参数,
大括号写逻辑,
要等加 async,
await 里面住。
总结
- 基础语法 :
function 名() {} - 异步语法 :
async function 名() {} - 调用方式 :直接写
函数名() - await 必须放在 async 函数里(最重要)
为什么优先用 const,不行再用 let
大白话 + 项目实战逻辑,超好理解。
一、核心一句话
const 更安全、更严谨;let 更随意、容易出 bug。 只要这个变量不会被重新赋值 ,就一律用 const。
二、分别看懂两个关键字
- const禁止「变量重新赋值」,只能读取,不能覆盖。
ts
const app = createApp(App)
app = 123 // ❌ 直接报错,防止误改
- let允许随便重新赋值,不受限制。
ts
let num = 1
num = 2 // ✅ 随便改,没人拦你
三、为什么推荐:优先 const
1. 防止不小心改错变量(最关键)
项目代码一多,很容易手滑、复制代码写错:
- 用
const:误重新赋值 → 直接报错,立马发现问题 - 用
let:误重新赋值 → 悄悄改掉,出现隐性 bug,极难排查
2. 代码语义清晰,一眼看懂意图
别人 / 你自己看代码:
- 看到
const→ 这个值固定不变,全局稳定 - 看到
let→ 这个值会被修改,要注意变化
比如:
ts
const app = createApp(App) // 一眼知道:app 实例永远不变
let count = 0 // 一眼知道:count 会累加变化
3. 符合前端 / TS 编码规范
所有主流规范(Vue、TS、ESLint)强制要求:
无重新赋值,必须使用
const
4. 不影响引用类型修改
重点误区纠正:const 不是锁死对象 / 数组内部数据,只是禁止变量替换
ts
const list = [1,2,3]
list.push(4) // ✅ 可以改内部数据
list = [] // ❌ 不能整体重新赋值
项目里的 Vue 实例、路由、对象、数组,完全不影响使用。
四、什么时候才用 let?
只有明确需要重新赋值时,才换成 let:
- 计数器、页码、开关状态
- 会动态覆盖的变量
ts
// 需要变 → 用 let
let page = 1
page = 2
// 永远不变 → 用 const
const router = useRouter()
五、极简总结(背诵版)
- const:只读、防误改、更安全、语义明确,优先使用。
- let:可修改,只给「会变的变量」用。
- 原则:不变就 const,要变再 let,var 直接抛弃。
一句话核心答案
一个 Vue 项目里,可以同时跑多个独立的 Vue 实例!
app.mount('#app')→ 挂载你的主页面goAppProvider.mount('#appProvider')→ 挂载一个独立的全局工具 / 弹窗 / 导航层
两个互不干扰,各管各的 DOM。
1. 先搞懂:mount 是干嘛的?
mount = 挂载 意思就是:把 Vue 管理的内容,贴到网页真实的 HTML 标签上。
Vue 本身是虚拟的,必须 mount 到一个真实的 HTML 元素,页面才能显示出来。
2. 你看到的两个挂载分别是什么?
① app.mount('#app')
这是你的项目主页面
plaintext
<div id="app"></div>
页面内容、路由、组件全都在这里。
② goAppProvider.mount('#appProvider', true)
这是第二个独立的 Vue 实例,专门用来放:
- 全局弹窗
- 全局消息提示
- 全局引导层
- 全局遮罩
- 全局状态控制层
它挂载到:
plaintext
<div id="appProvider"></div>
3. 为什么要搞两个实例?为什么不都放 #app 里?
因为要隔离!
- 主页面路由会切换、会刷新、会变化
- 但全局弹窗、提示、引导层不能跟着页面一起消失!
- 所以必须单独开一个 Vue 实例来管理
比喻:
#app= 房子里面的房间(会变、会换)#appProvider= 房子的天花板 / 灯 / 监控(永远在最上层,不随房间变化)
4. 后面的 true 是什么意思?
ts
mount('#appProvider', true)
true = 水合模式 / 兼容模式作用:
- 让挂载更稳定
- 防止 SSR / 静态页面报错
- 保持 DOM 结构不被破坏
你不用管它,照抄就行。
5. 最直白总结(你只要记住这个)
- 一个项目可以跑多个 Vue 实例
- app.mount('#app') → 挂载主页面
- goAppProvider.mount('#appProvider') → 挂载全局工具层(弹窗 / 提示)
- 两个实例互相独立、互不干扰
- 目的:让全局弹窗不被页面切换影响
超级总结
goAppProvider.mount('#appProvider') 的作用就是:
创建一个独立的全局 Vue 实例,专门管理全局弹窗、提示、引导层,永远显示在最上层,不随页面路由变化。
什么叫「独立的全局 Vue 实例」,为什么它不随页面变化、永远在最上层。
先看页面结构(你马上就懂)
你的网页 index.html 里一定长这样:
html
预览
<body>
<!-- 1. 全局顶层:永远不动 -->
<div id="appProvider"></div>
<!-- 2. 主页面:会切换、会刷新 -->
<div id="app"></div>
</body>
两个盒子的关系
- #appProvider = 顶层悬浮层(天花板)
- #app = 页面内容层(地板 / 房间)
一、主实例:app.mount ('#app') → 管页面
它负责:
- 页面内容
- 路由跳转
- 登录 / 首页 / 列表
- 切换页面时,这里面的内容会全部刷新
plaintext
路由切换 → #app 里面的内容全换掉
二、全局实例:goAppProvider.mount ('#appProvider') → 管悬浮
它负责:
- 全局弹窗
- 消息提示(toast)
- 加载动画
- 新手引导
- 页面怎么切换,它都不动、不消失、不刷新
plaintext
路由切换 → #appProvider 纹丝不动
二、最直观的场景(你一定见过)
场景:正在发送请求,弹出加载弹窗
- 你点提交 → 弹出 Loading 弹窗
- 页面跳转 / 路由切换
- Loading 还在!不会消失
为什么?因为弹窗属于 #appProvider,和页面不在一个实例里。
如果不做独立实例:
plaintext
路由一切换 → 弹窗跟着页面一起消失!
三、为什么要两个 Vue 实例?(核心原因)
一句话:
Vue 实例和它挂载的 DOM 是绑定的,一个实例管一个区域。
- 主实例管 #app → 页面会变
- 全局实例管 #appProvider → 永远不变、永远最上层
它们的关系:完全隔离
- 路由变化 → 不影响全局实例
- 页面刷新 → 不影响全局提示
- 主应用崩溃 → 全局弹窗还能正常报错
- 样式隔离、状态隔离、生命周期隔离
四、真实项目里长什么样?(代码超简单)
1. main.ts 里你看到的代码
ts
// 主应用
const app = createApp(App)
app.use(router)
await router.isReady()
app.mount('#app')
// 全局独立实例
const goAppProvider = createApp(GlobalProvider)
goAppProvider.mount('#appProvider', true)
2. GlobalProvider.vue 里面放什么?
vue
<template>
<!-- 全局弹窗 -->
<Dialog />
<!-- 消息提示 -->
<Toast />
<!-- 加载动画 -->
<Loading />
<!-- 新手引导 -->
<Guide />
</template>
3. 页面效果(永远悬浮在最上面)
plaintext
┌─────────────────────┐
│ 【全局提示:成功】 │ ← appProvider(不动)
└─────────────────────┘
┌─────────────────────┐
│ 首页 / 关于 / 我的 │ ← app(页面切换)
└─────────────────────┘
五、最通俗的比喻(一辈子不忘)
- #app = 电视机播放的节目(会换台、会变)
- #appProvider = 电视机外壳上的指示灯(换台不影响、永远亮着)
六、你只要记住这 3 句
- 一个项目可以跑多个 Vue 实例,互相独立
- goAppProvider 是专门管全局弹窗、提示、悬浮层的
- 它不随路由变化、不随页面刷新,永远在最上层显示
终极总结
goAppProvider.mount('#appProvider')就是:创建一个独立于主页面之外的、永远存在的顶层 Vue 应用,专门管理全局 UI,不被路由干扰。
我用最简单、最直观、一步一步的方式讲清楚:
一句话终极答案
页面(#app)通过一个 "全局通知工具" 喊话,全局实例(#appProvider)听到后,把弹框显示出来。
它们不直接接触 ,靠一个中间桥梁通信!
一、先看它们的位置(彻底隔离)
plaintext
页面(app) 全局实例(appProvider)
┌─────────────┐ ┌─────────────────┐
│ 点击按钮触发 │──→│ 弹框显示 │
└─────────────┘ └─────────────────┘
两个实例完全独立,不能直接调用方法!
二、通信靠什么?------ 全局事件总线(EventBus)
你可以把它理解成一个大喇叭 / 广播系统。
- appProvider(全局实例) 提前打开收音机,等着听消息
- 页面(app) 点击按钮 → 用大喇叭喊话:"打开弹框!"
- appProvider 听到消息 → 立刻显示弹框
这就是它们的配合方式!
三、超简单流程(3 步看懂)
第一步:全局实例 appProvider 提前 "监听" 消息
在全局组件里(比如 GlobalProvider.vue)
ts
// 监听:只要有人发 "openDialog" 事件,就执行弹框
eventBus.on('openDialog', (options) => {
showDialog(options) // 显示弹框
})
第二步:页面里点击按钮 "发送" 消息
在任意页面(首页 / 列表 / 个人中心)
ts
function handleClick() {
// 发送消息:告诉全局实例,打开弹框
eventBus.emit('openDialog', { title: '提示', content: '登录成功' })
}
第三步:全局实例收到消息 → 弹框出现
plaintext
页面发送事件 → 事件总线转发 → 全局实例接收 → 弹框显示
四、用生活比喻(秒懂)
- 页面(app)= 你
- eventBus = 手机
- appProvider = 外卖员
流程:
- 你(页面) 用手机发微信:"我要弹框!"
- 手机(事件总线) 把消息传给外卖员
- 外卖员(全局实例) 收到消息,把弹框送过来
你们不直接见面,靠手机通信!
五、真实项目里最常用的 2 种实现方式
你项目里 99% 是下面这两种之一:
方式 1:事件总线(mitt /tiny-emitter)
ts
// 页面发事件
emit('show:toast', '操作成功')
// 全局实例监听事件
on('show:toast', (msg) => {
显示提示框(msg)
})
方式 2:全局状态管理(Pinia / Vuex)
ts
// 页面修改状态
store.showDialog = true
// 全局实例监听状态变化
watch(store.showDialog, (val) => {
if(val) 显示弹框()
})
六、最终总结(你彻底通透了)
页面点击 → 弹框出现,完整链路:
- 你在 ** 页面(#app)** 点击按钮
- 页面通过 事件总线 / 状态库 发送一个通知
- 全局实例(#appProvider) 一直在监听这个通知
- 全局实例收到通知
- 弹框显示出来
最核心一句话(背会)
两个 Vue 实例不能直接调用对方, 靠 "事件总线" 或 "状态库" 当中间桥梁, 实现页面通知全局弹框。
我用最直白、最通俗、一看就懂 的方式,给你讲透:window['$vue'] = app 到底是干嘛的?有什么用?
一句话终极答案
把 Vue 实例 app 挂到 window 上,就是让: 网页里【任何地方、任何代码、任何文件】,都能随时拿到、调用这个 Vue 实例!
1. 先搞懂:window 是什么?
window = 浏览器全局老大 网页里所有 JS 代码,都能直接访问 window
不管你在:
- Vue 组件里
- 普通 js 文件
- 控制台
- 第三方插件
- 甚至别的框架代码
全都能摸到 window!
2. 为什么要把 app 挂到 window?
因为:
Vue 实例 app 默认只在 main.ts 里能用,出了文件就拿不到!
ts
// main.ts
const app = createApp(App) // 只在这个文件里有效
// 别的文件:拿不到 app!
// 想调用 app 的方法 → 做不到
一挂到 window:全世界都能用!
ts
window['$vue'] = app
现在:任何文件、任何地方、任何时间都能直接写:
ts
window.$vue.config.globalProperties
window.$vue.use(xxx)
window.$vue.provide(xxx)
3. 它具体实现什么功能?(最实用的 3 个场景)
场景 1:老项目、第三方 JS 库想用 Vue
有些第三方插件、老代码、非 Vue 文件想调用 Vue 的功能、全局方法、全局变量
没挂 window:
plaintext
第三方JS → 拿不到 Vue → 用不了
挂了 window:
plaintext
第三方JS → 直接用 window.$vue → 调用 Vue 功能
场景 2:浏览器控制台调试(超级常用!)
你打开浏览器 F12 控制台,直接输入:
js
$vue
window.$vue
能直接看到、操作 Vue 实例!
比如:
js
// 控制台调用 Vue 全局方法
$vue.config.globalProperties
// 调用 app 内的方法
$vue.mount(...)
方便调试、查问题、看数据!
场景 3:非模块环境调用 Vue
有些代码不是 ES6 模块没法 import、没法导入只能通过 window 拿东西
比如:
- 外链 JS
- 广告代码
- 统计脚本
- 历史遗留代码
它们想用到你 Vue 项目里的东西只能通过 window.$vue
4. 最通俗的比喻(一辈子不忘)
- app = 你的手机
- main.ts = 你的房间
- window = 大街
默认情况:手机放在房间里,外面的人拿不到。
window['$vue'] = app= 你把手机放到大街上,谁都能拿来用!
5. 总结(你 100% 懂了)
window['$vue'] = app 作用:
- 让 Vue 实例从 "仅限 main.ts 使用" → 变成 "全局可访问"
- 第三方 JS、老代码、控制台 → 都能操作 Vue
- 方便调试、方便兼容、方便跨脚本调用
一句话记住:
把 Vue 实例暴露到浏览器全局,让任何代码都能随时调用它!
超级总结
你不用纠结复杂原理,记住这句就够:
window 是全局,挂上去 = 谁都能用!
一句话核心
then = 等前面的异步事情做完了,再做后面的事!
1. 先看你代码里的场景
ts
appInit().then(() => {
initFunction()
})
翻译成人话:
先等 appInit 执行完成(路由加载、页面挂载都结束), 然后再执行 initFunction 初始化!
2. 为什么要用 then?
因为 appInit() 是 async 异步函数,它不会立刻跑完!
如果你直接写:
ts
appInit() // 异步,还没跑完
initFunction() // 马上就执行了 ❌ 顺序乱了
结果:页面还没挂载好 → 初始化函数先跑 → 报错!
加了 then:
ts
appInit().then(() => {
initFunction() // 等完全启动完再执行 ✅
})
3. then 最标准语法(背会)
ts
异步函数().then(执行成功后要做的事)
4. 最通俗比喻(秒懂)
appInit()= 点外卖then= 外卖送到后initFunction()= 吃饭
ts
点外卖().then(() => {
吃饭()
})
必须送到了才能吃!不能没送到就吃!
5. 它和 await 是一样的东西!
then 是 Promise 语法await 是 async/await 语法
作用一模一样:等异步完成再执行!
你这段代码也可以写成:
ts
await appInit()
initFunction()
和
ts
appInit().then(() => {
initFunction()
})
完全等价!
6. 超级记忆口诀
plaintext
异步后面加 then,
等它完事再执行。
顺序不乱不报错,
异步流程稳稳稳。
总结(你 100% 懂了)
then是用来等待异步操作完成的appInit().then(...)= 等项目完全启动完,再执行后续初始化- 保证执行顺序不乱、不报错
- 和 await 功能一样,写法不同
一句话记住:then = 等前面异步做完,再做后面!
为什么 TypeScript 能让代码更稳定、提示更强?
一句话终极答案
TS = 给 JS 套上 "规则和检查", 写错代码直接报错、写代码自动提示, 从根源避免 80% 的低级 bug!
一、先懂核心:JS 是 "无规则" 的,TS 是 "有规则" 的
普通 JavaScript(混乱、容易崩)
你写什么它都接受,错了也不告诉你,运行才炸。
js
let age = 18
age = '十八岁' // ✅ JS 不管,随便改
age() // ✅ JS 不拦你,运行直接崩溃!
TypeScript(严格、安全)
你必须规定类型 ,写错直接红线提醒,运行前就拦截错误。
ts
let age: number = 18
age = '十八岁' // ❌ 直接报错!不能把字符串给数字
age() // ❌ 直接报错!数字不是函数
二、为什么 TS 让代码【更稳定】?
1. 提前发现错误(运行前就拦死 bug)
JS:
- 写完 → 运行页面 → 控制台报错 → 找半天TS:
- 写完 立刻红线提示 → 改对才能继续90% 的拼写错误、类型错误、调用错误,直接消失!
2. 规定数据类型,不乱来
比如:
ts
function add(a: number, b: number) {
return a + b
}
- 必须传数字
- 传字符串 / 布尔值 → 直接报错
- 不会出现
1 + '1' = '11'这种诡异 bug
3. 重构代码更安全
改函数名、改参数、改字段TS 会自动检查所有用到的地方 一改错全标红,不会改完偷偷崩
三、为什么 TS 让代码【提示更强】?
1. 你写代码,TS 自动 "猜你要写什么"
比如你定义一个对象:
ts
const user = { name: '小明', age: 18 }
你再写:
plaintext
user.
TS 会自动弹出提示 :name、age不用记字段,不用翻文件,不用查文档
2. 函数参数提示
写函数时,TS 直接告诉你:
- 要传几个参数
- 每个参数是什么类型
- 函数返回什么
不用背 API,不用记逻辑,编辑器帮你完成
3. 跳到定义、自动补全、自动纠错
VSCode + TS = 全自动写代码
- 自动补全单词
- 自动补全函数
- 自动识别错误
- 一键跳转到源码
四、最通俗比喻(一辈子不忘)
JavaScript = 不戴头盔、不系安全带、随便开
- 随便撞
- 错了才知道
- 容易出事
TypeScript = 全套安全带 + 安全气囊 + 导航 + 防撞预警
- 还没撞就提醒
- 规定路线
- 自动纠错
- 稳定、安全、不翻车
五、超级总结(背会这 3 点)
TypeScript 为什么好?
-
更稳定
- 加类型约束,不乱来
- 错误提前暴露,运行不崩溃
- 重构、协作更安全
-
提示更强
- 自动补全代码
- 自动提示字段 / 方法
- 不用记,不用查,写得飞快
-
大型项目必备
- 代码不容易乱
- 多人协作不互坑
- 维护成本极低
最终一句话(你彻底懂了)
TS 就是给 JS 加上规则和智能提示, 让代码不容易写错、写错了马上知道、写起来还更快!
所以现在所有企业、Vue、React 项目全部强制使用 TS。
你现在完全理解为什么说 TS 更稳定、提示更强 了吧!
一句话终极答案
把一大段臃肿的代码,拆成好几个小文件, 每个小文件只干一件事, 通过传 app 进去,完成全局注册。 目的:代码干净、好维护、不乱!
先看你这几行代码
ts
setupNaive(app) // 注册 UI 库
setupDirectives(app) // 注册全局指令
setupStore(app) // 注册状态管理
setupRouter(app) // 注册路由
它们本质就是:把 app 实例传给函数 → 函数内部帮你挂载插件 → 代码拆分更清晰
一、不拆分的话,代码有多乱?(对比一下)
如果不拆分成函数,你的 main.ts 会变成几百行一坨屎:
ts
// main.ts
const app = createApp()
// 一大段 UI 库配置
app.use(xxx)
app.config.globalProperties.xxx = xxx
// 一大段路由配置
app.use(router)
router.beforeEach(xxx)
// 一大段状态管理
app.use(pinia)
pinia.use(xxx)
// 一大段自定义指令
app.directive('xxx', ...)
app.directive('yyy', ...)
// 几百行代码堆在一起 ❌ 乱、难维护
这就是没人愿意维护的烂代码!
二、拆成函数后,有多干净?(这就是你看到的写法)
ts
// main.ts 超级清爽
const app = createApp()
setupNaive(app)
setupRouter(app)
setupStore(app)
setupDirectives(app)
app.mount('#app')
每个函数对应一个文件
setupNaive→ 管 UI 组件库setupRouter→ 管路由setupStore→ 管状态setupDirectives→ 管全局指令
各司其职,互不干扰!
三、函数内部到底干了啥?
举个例子:setupRouter(app)
setupRouter.ts
ts
import router from './router'
// 接收 app 实例
export function setupRouter(app) {
// 给 app 注册路由
app.use(router)
}
main.ts
ts
import { setupRouter } from './setupRouter'
// 把 app 传进去
setupRouter(app)
等于:
ts
app.use(router)
只是把代码搬到外面去了!
四、这种写法叫什么?
官方名字:
Vue 插件式编程 + 函数拆分
大白话解释:
- 插件式:把功能做成独立插件,想装就装、想卸就卸
- 函数传参:把 app 实例传给函数,让函数去配置
- 好处 :
- main.ts 清爽到爆炸
- 功能模块化
- 好维护、好删除、好修改
五、最通俗比喻(一辈子不忘)
- app = 手机
- setupRouter = 安装微信
- setupStore = 安装支付宝
- setupNaive = 安装主题
- setupDirectives = 安装插件
ts
安装微信(手机)
安装支付宝(手机)
安装主题(手机)
手机还是那个手机,只是一个个安装功能!
六、你只要记住 3 点
- 这些函数都是用来给 app 安装 "全局功能" 的
- 把 app 传进去 = 给这个 Vue 实例安装插件
- 目的:代码拆分、清爽、好维护
终极总结(你 100% 懂了)
ts
setupNaive(app)
setupDirectives(app)
setupStore(app)
setupRouter(app)
意思就是: 把 Vue 实例 app 传给这些工具函数, 让它们分别给 app 注册路由、状态、UI 库、指令, 让 main.ts 代码清爽、干净、好维护!
这就是企业级项目最标准的写法!
一句话终极答案
<router-view /> = 页面内容的 显示容器 **路由切换时,页面组件就从这里 "放进去 / 替换" 它就是页面的占位符、画框、容器!
一、先搞懂:它到底是干嘛的?
你写在 App.vue 里:
vue
<template>
<!-- 导航栏(永远不变) -->
<Navbar />
<!-- 路由插槽:页面在这里切换! -->
<router-view />
<!-- 底部(永远不变) -->
<Footer />
</template>
效果:
- 导航 + 底部 永远不动
- 只有 中间的
<router-view>会跟着路由切换页面 - 首页、列表、我的页面,全都在这里显示
这就是路由插槽。
二、插槽是什么?(超简单)
插槽 = 预留位置 <router-view> 就是 VueRouter 给你预留的页面位置。
三、<router-view> 有哪几种插槽?
它一共 2 种插槽:
1. 默认插槽(最常用)
就是你写的:
html
预览
<router-view />
作用:显示当前路由匹配的页面所有项目 99% 时间都用这个。
2. 命名插槽(用来做嵌套路由)
比如:
html
预览
<router-view name="sidebar" />
<router-view name="main" />
作用:一个页面里,同时显示多个路由组件(极少用,后台布局偶尔用)
四、最重要的:什么是嵌套路由?
这是 <router-view> 最核心的用法!
结构:
plaintext
App.vue
↓
<router-view> → 显示 Layout(后台框架)
↓
Layout.vue
↓
<router-view> → 显示真正页面(首页/列表)
意思:
- 第一层:放整体布局(侧边栏 + 头部)
- 第二层:放内容页面
这就叫嵌套路由。
五、默认插槽 vs 命名插槽:区别是什么?
表格
| 类型 | 写法 | 作用 | 使用频率 |
|---|---|---|---|
| 默认插槽 | <router-view> |
显示当前路由页面 | ✅ 99% |
| 命名插槽 | <router-view name="xxx"> |
一个路由同时显示多个组件 | ❌ 极少 |
你只需要记住默认插槽!
六、最通俗比喻(一辈子不忘)
<router-view>= 电视机屏幕- 路由 = 换台
- 页面组件 = 节目
换台(切换路由)→ 节目变了(页面变了)→ 屏幕没变(<router-view> 位置没变)
七、你只要记住 3 句
<router-view>= 页面显示的位置- 路由切换 → 页面从这里替换
- 只有默认插槽最常用,命名插槽几乎不用
终极总结(你 100% 懂了)
<router-view /> 是什么?
页面内容的容器、占位符、画框。
有什么插槽?
- 默认插槽:显示页面(必须会)
- 命名插槽:多面板同时显示(几乎不用)
作用?
路由切换页面,就是切换这里显示的内容!
一、为什么「组件通信」是超级重点?
Vue 是组件化开发 :页面拆成:父组件、子组件、孙组件、兄弟组件、跨层级组件、全局组件。组件都是独立隔离的,数据默认互不互通。
组件本身互不认识,想要互相传数据、调用方法,就必须靠「组件通信」。
你写的所有项目:
- 弹窗传参
- 列表点击修改
- 侧边栏收缩
- 全局提示、权限、菜单全靠 组件通信。
二、Vue3 所有组件关系 & 对应通信方案(背这个就够)
1. 父子组件(最常用)
- 父 → 子:props 传参
- 子 → 父:defineEmits 自定义事件
- 父拿子实例 / 方法:defineExpose + ref
2. 跨层级 / 深层祖孙(隔好几层)
- provide / inject 依赖注入不用一层一层 props 透传,深层组件直接拿数据。
3. 兄弟组件 / 毫无关系的组件
- Pinia(全局状态仓库) 首选
- 小型简单项目:mitt 事件总线
4. 全局任意组件、跨实例通信(你前面问的双 app 实例)
- 全局事件总线 mitt
- 全局挂载 window 变量
- Pinia 全局状态
三、难度 & 重要度排序
- ⭐⭐⭐⭐⭐ 必掌握props /emit/ Pinia
日常开发 90% 场景全靠这三个
- ⭐⭐⭐⭐ 高频provide /inject
后台管理、布局嵌套、深层组件必用
- ⭐⭐⭐ 了解即可mitt 事件总线、ref + expose、全局挂载
四、结合你当前项目举例
- 页面组件 给 弹窗组件传数据 → props
- 子组件关闭弹窗通知父组件 → emit
- 深层菜单、主题、全局配置 → provide/inject
- 所有组件共享用户信息、登录状态 → Pinia
- 主 app 和 独立全局弹窗 app 通信 → 事件总线
全覆盖,全部都是组件通信。
五、一句话总结
- 组件通信 Vue 核心重点,比基础语法重要得多;
- 层级越多、项目越大,越依赖通信方案;
- 面试必问:父子、兄弟、跨层级分别怎么传值;
- 你毕设、写项目、改 bug,天天都要用到。
Vue3 组件通信「全套极简模板」
(适合背诵、写笔记、毕设、面试,组合式 API + TS)
一、父子组件 👨👦 最核心、最高频
1. 父传子:props
子组件接收
vue
// 子组件 Child.vue
<script setup lang="ts">
const props = defineProps<{
msg: string
count: number
}>()
</script>
父组件传值
vue
// 父组件
<Child msg="你好" :count="100" />
2. 子传父:defineEmits
子组件触发事件
vue
// 子组件
<script setup lang="ts">
const emit = defineEmits<{
(e: 'close', val: boolean): void
}>()
// 触发
const handleClose = () => {
emit('close', true)
}
</script>
父组件接收
vue
<Child @close="handleClose" />
3. 父调用子方法 / 获取子实例:ref + defineExpose
子组件
vue
<script setup lang="ts">
const open = () => {
console.log('打开弹窗')
}
// 暴露出去
defineExpose({ open })
</script>
父组件
vue
<Child ref="childRef" />
<script setup lang="ts">
import { ref } from 'vue'
const childRef = ref()
// 调用子组件方法
childRef.value?.open()
</script>
二、跨层级 / 深层祖孙(隔多层):provide /inject
上层祖先组件
ts
// 祖先
import { provide, ref } from 'vue'
const theme = ref('dark')
provide('themeKey', theme)
任意深层后代组件,直接取值
ts
// 后代(不用一层层传props)
import { inject } from 'vue'
const theme = inject('themeKey')
适用:布局全局配置、主题、侧边栏状态
三、兄弟组件 / 无关联组件:Pinia(首选)
全局共享数据,所有组件都能读、改
- 定义仓库
ts
// stores/user.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
nickname: ''
})
})
- 组件 A 修改数据
ts
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
userStore.nickname = '张三'
- 组件 B 直接使用
ts
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
console.log(userStore.nickname)
四、简单项目兄弟通信:mitt 事件总线
- 新建
utils/event.ts
ts
import mitt from 'mitt'
export const emitter = mitt()
- 组件 A 发送事件
ts
import { emitter } from '@/utils/event'
emitter.emit('refreshList', '参数')
- 组件 B 监听事件
ts
import { emitter } from '@/utils/event'
emitter.on('refreshList', (val) => {
console.log('收到:', val)
})
适合:轻量弹窗、跨页面简单通知
五、全局通信 / 多 Vue 实例通信(你项目的双 app)
ts
// 挂载到window全局
window.$vue = app
// 任意地方直接使用
window.$vue.xxx
用来实现:主项目 ↔ 全局弹窗独立实例 互相通信
六、快速记忆总结(背诵版)
- 父子传值:props 下行,emit 上行
- 父操作子:ref + defineExpose
- 多层级嵌套:provide / inject
- 兄弟 / 全局共享:Pinia(首选)
- 简单跨组件通知:mitt 事件总线
- 跨独立 Vue 实例:window 全局 + 事件
Vue3 <script setup> 最新写法 vs 旧写法(超级清晰对比)
我给你用最简单、最直观、一看就懂 的方式讲清楚:现在的 <script setup> 到底比以前好在哪、区别是什么!
一句话终极结论
<script setup> = 简化 90% 代码、不用写多余语法、自动暴露内容、性能更好、官方唯一推荐写法。
一、最直观:代码量对比(你一眼就懂)
旧写法(Vue3 原始写法)
vue
<script lang="ts">
import { ref, defineComponent } from 'vue'
export default defineComponent({
setup() {
const name = ref('小明')
function sayHi() {
console.log(name.value)
}
// 必须 return 才能在 template 用
return {
name,
sayHi
}
}
})
</script>
最新写法 <script setup>(现在用的)
vue
<script lang="ts" setup>
import { ref } from 'vue'
// 直接写!
const name = ref('小明')
function sayHi() {
console.log(name.value)
}
</script>
区别:少了一大坨骨架代码!
二、核心区别(背会这 5 点就够)
1. 不用写 export default / defineComponent
- 旧:必须写一大坨骨架
- 新:直接写代码,干净清爽
2. 不用写 setup() {} 函数
- 旧:所有代码必须塞在
setup()里 - 新:整个 script 就是 setup
3. 不用 return { } 暴露变量
- 旧:定义的变量 / 函数必须 return,template 才能用
- 新:定义了就能直接用,自动暴露
4. 组件自动注册
- 旧:
import Comp from ...还要放进components: {} - 新:import 完直接用,不用注册
5. 语法更简洁、TS 支持更好
- 旧:TS 类型写起来麻烦
- 新:TS 天然完美支持
三、最常用功能对比(超级实用)
1. 使用组件
旧写法:
ts
import Child from './Child.vue'
export default {
components: { Child } // 必须注册
}
新写法:
ts
import Child from './Child.vue' // 直接用 ✅
2. 父子传参 props
旧写法:
ts
export default {
props: { msg: String }
}
新写法:
ts
defineProps({ msg: String })
3. 子传父 emit
旧写法:
ts
setup(props, { emit }) {
emit('test')
}
新写法:
ts
const emit = defineEmits(['test'])
emit('test')
四、为什么官方强制推荐 <script setup>?
- 代码更少,不用写冗余模板
- 性能更好,底层优化更强
- TS 支持更强,类型提示完美
- 开发更快,少写很多重复代码
- 未来趋势,旧写法逐渐淘汰
五、最通俗比喻
- 旧写法 = 填写复杂表格,要填各种框、签名、盖章
- 新写法 = 直接写字,想写啥写啥
六、超级记忆口诀(背会)
plaintext
script setup 真正强,
不用 return 不用装,
组件直接引即用,
代码简洁不慌张!
终极总结(你 100% 懂了)
Vue3 最新写法 <script setup> 和旧写法的区别:
- 不用写 export default /defineComponent
- 不用写 setup () 函数
- 不用 return 暴露变量
- 组件不用注册,import 直接用
- 代码更少、性能更好、TS 更强
一句话终极答案
点击切换 → 改一个全局变量(light/dark) → 给 HTML/body 加个类名 → CSS 根据类名自动换颜色!
整个项目所有页面,靠 CSS 变量 统一变色,不是一个个组件去改!
一、超简单流程(3 步看懂)
1. 你点击切换按钮
ts
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
2. 全局给 body 加个类名
html
预览
<body class="dark"> <!-- 或者 light -->
3. CSS 变量自动变色(所有界面一起变)
css
:root {
--bg-color: #fff;
--text-color: #333;
}
.dark {
--bg-color: #181818;
--text-color: #fff;
}
所有页面都用:
css
background: var(--bg-color);
color: var(--text-color);
点一下 → 类名变 → 所有颜色自动变!
二、核心技术只有 2 个
1. CSS 变量(全局颜色统一管理)
css
:root {
--color-primary: #1890ff;
--bg: #ffffff;
}
.dark {
--color-primary: #0050b3;
--bg: #141414;
}
2. 全局状态管理(Pinia)控制主题
ts
// store/theme.ts
const useThemeStore = defineStore('theme', {
state: () => ({
mode: 'light'
}),
actions: {
toggle() {
this.mode = this.mode === 'light' ? 'dark' : 'light'
document.body.className = this.mode
}
}
})
任何页面点击切换 → 全局 body 类名变化 → 全站颜色变化!
三、为什么 "很多界面一下子都变了"?
因为:所有组件、所有页面、所有布局,都用的是同一套 CSS 变量!
- 按钮颜色
- 背景色
- 文字颜色
- 边框、阴影、卡片
- 左侧菜单
- 顶部导航
全部用 var (--xxx)
所以:变量一变 → 全部一起变! 不是一个个改,是全局统一生效。
四、最通俗比喻(一辈子不忘)
- CSS 变量 = 油漆桶
- light /dark = 浅色油漆 / 深色油漆
- 点击切换 = 换一桶油漆
- 所有页面 = 墙壁
换一桶油漆 → 所有墙壁自动变成新颜色!
五、真实项目主题切换完整流程(你项目里就是这样)
- Pinia 存储当前主题 light/dark
- 点击按钮 → 切换主题
- 给 body 加 class = dark /light
- CSS 根据类名切换变量值
- 全站所有组件自动变色
- localStorage 保存主题,刷新不丢失
六、超级总结(背会这 3 句)
- 主题切换 = 切换 body 上的一个类名
- 所有颜色靠 CSS 变量统一管理
- 类名一变 → 全站自动变色
最终一句话
主题切换不是一个个改页面, 而是切换一个 "全局开关", 让所有 CSS 变量自动换颜色!
自定义 Hook 到底是什么?(超级通俗、彻底讲懂)
我用最简单、最直白、你一听就懂的方式告诉你:
一句话终极答案
Hook = 把 "重复的功能逻辑" 打包成一个函数, 哪里要用,直接调用, 让组件代码更干净、逻辑更清晰、可复用。
Hook 本质就是一个普通函数! 只是它专门用来封装 Vue 逻辑。
一、先看你项目里的 Hook
ts
const darkTheme = useDarkThemeHook() // 封装暗黑模式逻辑
const overridesTheme = useThemeOverridesHook() // 封装主题色配置
const hljsTheme = useCode() // 封装代码高亮
const { locale, dateLocale } = useLang() // 封装国际化语言
这些全都是 自定义 Hook。
二、Hook 是什么?(大白话)
把一个功能的所有代码: 变量、方法、计算、生命周期、状态...... 全部打包到一个函数里, 这个函数就叫 Hook。
就像:
- 用手机拍照 → 不用自己组装相机
- 直接点一下相机功能Hook 就是封装好的 "相机功能",你直接用就行。
三、Hook 有什么功能?(核心 4 个)
1. 逻辑复用(最强大)
同样的逻辑,10 个组件要用,不用写 10 遍,封装一次 Hook,到处调用。
2. 让组件代码超级干净
组件里只写:
ts
const xxx = useXxx()
逻辑全藏在 Hook 里,组件清爽到爆炸。
3. 分离业务逻辑
- 主题逻辑 → 放 useTheme
- 语言逻辑 → 放 useLang
- 表格逻辑 → 放 useTable
- 弹窗逻辑 → 放 useModal
各司其职,不乱。
4. 可以使用 Vue 所有功能
Hook 内部可以用:
- ref / reactive
- computed
- watch
- onMounted
- 生命周期......
和组件里一模一样!
四、举个超级简单例子(你马上懂)
假如我要封装一个 "暗黑模式" Hook
1. 创建 Hook:useDark.ts
ts
// 这就是一个自定义 Hook
import { ref, computed } from 'vue'
export function useDarkThemeHook() {
// 状态
const isDark = ref(false)
// 切换方法
const toggleDark = () => {
isDark.value = !isDark.value
}
// 返回给组件使用
return {
isDark,
toggleDark
}
}
2. 在组件里使用
ts
import { useDarkThemeHook } from './useDarkTheme'
// 调用 Hook,拿到状态和方法
const { isDark, toggleDark } = useDarkThemeHook()
这就是 Hook!
五、为什么企业项目必须用 Hook?
- 代码不重复
- 好维护(改一个 Hook,全部生效)
- 组件更干净
- 逻辑清晰
- 方便团队协作
这就是你看到的:
ts
const darkTheme = useDarkThemeHook()
六、Hook 的命名规则(必须记住)
所有 Hook 都以 use 开头
- useDark
- useTheme
- useLang
- useUserInfo
- useTable
- useModal
看到 useXxx() → 就是 Hook!
七、最通俗比喻(一辈子不忘)
- Hook = 工具箱
- 每个 Hook 是一个专用工具
- 组件 = 工人
- 工人不用造工具,直接拿工具箱使用
终极总结(你 100% 懂了)
Hook 是什么?
一个封装了 Vue 逻辑(状态、方法、计算属性...)的函数。
功能是什么?
- 复用逻辑
- 简化组件
- 分离代码
- 让项目更规范、更干净
一句话记住:
use 开头的函数,就是 Vue 组合式 Hook,用来封装功能!