语法大全-only-writer

ES6+ 模块化 import 大括号规则(极简版)

这是前端 / TS 最基础、最常用的知识点,只看导出方式,和导入没关系,一句话总结:

核心规则

  1. 导入默认导出 → 不加 {}
  2. 导入命名导出 → 必须加 {}

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'     // ✅ 必须加 {}

快速记忆口诀

默认导出不带花,命名导出必须花


总结

  1. 看到 export default → 导入不加 {}
  2. 看到 export 变量/函数 → 导入必须加 {}
  3. 一个文件可以同时有默认导出 + 多个命名导出

async /await 到底是什么?

一句话:让异步代码(需要等待的代码)写起来像同步代码一样舒服、好读、好控制顺序。

  • async:放在函数前面 → 告诉 JS "这是一个异步函数"
  • await:放在异步操作前面 → 告诉 JS"等我做完,再往下走"

核心用法(背会这 2 条就够)

  1. await 必须写在 async 函数里
  2. 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()

它到底做了什么?

  1. 不等待:页面直接挂载 → 路由还没加载好 → 页面闪、报错、跳转异常
  2. 加 await 等待 :路由加载完成 → 再挂载页面 → 流程稳定、不报错

最通俗的比喻

  • async = 我要做一件需要等的事
  • await = 等一下!先别往下跑!

就像:

ts

复制代码
async function 吃饭() {
  await 做饭()    // 必须等饭做好
  再吃()          // 饭好了才执行
}

通用写法模板(任何地方都能用)

ts

复制代码
// 1. 函数前面加 async
async function 函数名() {
  // 2. 异步操作前面加 await
  await 异步操作()

  // 3. 等待完成后,才会执行这里
  console.log('等待完成!继续执行')
}

超简记忆口诀

async 包函数,await 等异步,顺序不乱跑,代码更安全。


总结

  1. async 写在函数前 → 开启异步函数
  2. await 写在异步操作前 → 等待完成再往下
  3. Vue 中必须写 await router.isReady () → 保证路由加载完再挂载页面,避免报错

为什么必须等路由加载完,再挂载页面? 不等会出什么问题?

一句话核心原因

路由没准备好,页面就先渲染 = 一定会出跳转错误、刷新白屏、导航守卫不生效。


1. 最常见的崩溃场景(你一定遇到过)

你刷新页面时:

  1. 浏览器一刷新 → 页面瞬间挂载
  2. 路由还在初始化(没准备好)
  3. 页面一上来就要跳转 / 读取当前路由
  4. 直接报错:导航守卫未执行、跳转失败、白屏

典型报错:

plaintext

复制代码
Uncaught (in promise) Error: Navigation cancelled from "/" to "/home"

或者

plaintext

复制代码
Cannot read property 'push' of undefined

2. 路由没准备好时,它 "缺" 什么?

router.isReady() 等待的是这 3 件事:

  1. 所有路由规则加载完成
  2. 初始导航已经执行完毕(第一次进入页面的跳转完成)
  3. 全局守卫(beforeEach)全部执行完

这三件事没做完,路由就是 "残废" 的。


3. 不等的后果(真实项目必踩坑)

① 刷新页面白屏 / 跳转不生效

用户一刷新 → 页面渲染了,但路由还没准备好 → 不知道当前应该显示哪个页面

② 全局守卫(权限判断)不执行

比如你写了登录验证:

ts

复制代码
router.beforeEach((to, from, next) => {
  // 权限判断
})

不等路由准备好,守卫根本不执行!→ 直接导致权限失效、页面越权访问。

③ 报错:Navigation cancelled

最经典的路由报错,100% 是因为没等路由 ready 就挂载页面

④ 服务端渲染 / 预渲染失效

SSR / SSG 项目,不等路由直接渲染会直接崩溃。


4. 等了之后有什么好处?

  1. 路由初始化完成
  2. 第一次跳转完成
  3. 守卫全部执行完
  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()

为什么?

因为:路由没准备好 → 页面渲染就是错的。


超级总结

  1. 不等路由 ready → 刷新白屏、跳转报错、权限失效
  2. 等路由 ready → 保证第一次导航完成、守卫执行、页面稳定
  3. 这是 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 点)

  1. 最主要原因await 必须放在 async 函数 里,不能直接写
  2. 代码整洁:把启动逻辑打包在一起,清晰不乱
  3. 可控性强:可延迟、可重试、可捕获错误

所以:不是想封装,是必须封装!

五、const 常量声明(最清晰版)

我给你讲最简单、最实用、面试必问的版本,保证你一看就懂、一用就会。


1. 为什么这里要用 const?

一句话:创建出来的 app 实例,全程不需要重新赋值,所以用 const。

ts

复制代码
const app = createApp(App)
  • createApp(App)创建一个 Vue 应用实例
  • 这个实例创建后就不会再变了
  • 全程只需要这一个 app,不会再写 app = xxx
  • 所以用 const(常量) 最安全、最规范

2. JS/TS 声明变量的 3 个关键字

只有 3 个

  1. var(古老、废弃、别用)
  2. let(变量:可以改)
  3. 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 → 永远不用

超级总结

  1. 为什么用 const 声明 app? 因为实例创建后不会重新赋值,const 最安全。

  2. 声明变量的 3 个关键字:

    • var(废弃)
    • let(变量,可改)
    • const(常量,不可重新赋值,首选)
  3. 规则: 不变 → 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 里面住。

总结

  1. 基础语法function 名() {}
  2. 异步语法async function 名() {}
  3. 调用方式 :直接写 函数名()
  4. await 必须放在 async 函数里(最重要)

为什么优先用 const,不行再用 let

大白话 + 项目实战逻辑,超好理解。

一、核心一句话

const 更安全、更严谨;let 更随意、容易出 bug。 只要这个变量不会被重新赋值 ,就一律用 const


二、分别看懂两个关键字

  1. const禁止「变量重新赋值」,只能读取,不能覆盖。

ts

复制代码
const app = createApp(App)
app = 123  // ❌ 直接报错,防止误改
  1. 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()

五、极简总结(背诵版)

  1. const:只读、防误改、更安全、语义明确,优先使用。
  2. let:可修改,只给「会变的变量」用。
  3. 原则:不变就 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. 最直白总结(你只要记住这个)

  1. 一个项目可以跑多个 Vue 实例
  2. app.mount('#app') → 挂载主页面
  3. goAppProvider.mount('#appProvider') → 挂载全局工具层(弹窗 / 提示)
  4. 两个实例互相独立、互不干扰
  5. 目的:让全局弹窗不被页面切换影响

超级总结

goAppProvider.mount('#appProvider') 的作用就是:

创建一个独立的全局 Vue 实例,专门管理全局弹窗、提示、引导层,永远显示在最上层,不随页面路由变化。

什么叫「独立的全局 Vue 实例」,为什么它不随页面变化、永远在最上层。

先看页面结构(你马上就懂)

你的网页 index.html 里一定长这样:

html

预览

复制代码
<body>
  <!-- 1. 全局顶层:永远不动 -->
  <div id="appProvider"></div>

  <!-- 2. 主页面:会切换、会刷新 -->
  <div id="app"></div>
</body>

两个盒子的关系

  1. #appProvider = 顶层悬浮层(天花板)
  2. #app = 页面内容层(地板 / 房间)

一、主实例:app.mount ('#app') → 管页面

它负责:

  • 页面内容
  • 路由跳转
  • 登录 / 首页 / 列表
  • 切换页面时,这里面的内容会全部刷新

plaintext

复制代码
路由切换 → #app 里面的内容全换掉

二、全局实例:goAppProvider.mount ('#appProvider') → 管悬浮

它负责:

  • 全局弹窗
  • 消息提示(toast)
  • 加载动画
  • 新手引导
  • 页面怎么切换,它都不动、不消失、不刷新

plaintext

复制代码
路由切换 → #appProvider 纹丝不动

二、最直观的场景(你一定见过)

场景:正在发送请求,弹出加载弹窗

  1. 你点提交 → 弹出 Loading 弹窗
  2. 页面跳转 / 路由切换
  3. 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 句

  1. 一个项目可以跑多个 Vue 实例,互相独立
  2. goAppProvider 是专门管全局弹窗、提示、悬浮层的
  3. 它不随路由变化、不随页面刷新,永远在最上层显示

终极总结

goAppProvider.mount('#appProvider')就是:创建一个独立于主页面之外的、永远存在的顶层 Vue 应用,专门管理全局 UI,不被路由干扰。

我用最简单、最直观、一步一步的方式讲清楚:

一句话终极答案

页面(#app)通过一个 "全局通知工具" 喊话,全局实例(#appProvider)听到后,把弹框显示出来。

它们不直接接触 ,靠一个中间桥梁通信!


一、先看它们的位置(彻底隔离)

plaintext

复制代码
  页面(app)        全局实例(appProvider)
┌─────────────┐   ┌─────────────────┐
│ 点击按钮触发 │──→│     弹框显示     │
└─────────────┘   └─────────────────┘

两个实例完全独立,不能直接调用方法!


二、通信靠什么?------ 全局事件总线(EventBus)

你可以把它理解成一个大喇叭 / 广播系统

  1. appProvider(全局实例) 提前打开收音机,等着听消息
  2. 页面(app) 点击按钮 → 用大喇叭喊话:"打开弹框!"
  3. 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 = 外卖员

流程:

  1. 你(页面) 用手机发微信:"我要弹框!"
  2. 手机(事件总线) 把消息传给外卖员
  3. 外卖员(全局实例) 收到消息,把弹框送过来

你们不直接见面,靠手机通信!


五、真实项目里最常用的 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) 显示弹框()
})

六、最终总结(你彻底通透了)

页面点击 → 弹框出现,完整链路:

  1. 你在 ** 页面(#app)** 点击按钮
  2. 页面通过 事件总线 / 状态库 发送一个通知
  3. 全局实例(#appProvider) 一直在监听这个通知
  4. 全局实例收到通知
  5. 弹框显示出来

最核心一句话(背会)

两个 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 作用:

  1. 让 Vue 实例从 "仅限 main.ts 使用" → 变成 "全局可访问"
  2. 第三方 JS、老代码、控制台 → 都能操作 Vue
  3. 方便调试、方便兼容、方便跨脚本调用

一句话记住:

把 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% 懂了)

  1. then 是用来等待异步操作完成的
  2. appInit().then(...) = 等项目完全启动完,再执行后续初始化
  3. 保证执行顺序不乱、不报错
  4. 和 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 为什么好?

  1. 更稳定

    • 加类型约束,不乱来
    • 错误提前暴露,运行不崩溃
    • 重构、协作更安全
  2. 提示更强

    • 自动补全代码
    • 自动提示字段 / 方法
    • 不用记,不用查,写得飞快
  3. 大型项目必备

    • 代码不容易乱
    • 多人协作不互坑
    • 维护成本极低

最终一句话(你彻底懂了)

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 插件式编程 + 函数拆分

大白话解释:

  1. 插件式:把功能做成独立插件,想装就装、想卸就卸
  2. 函数传参:把 app 实例传给函数,让函数去配置
  3. 好处
    • main.ts 清爽到爆炸
    • 功能模块化
    • 好维护、好删除、好修改

五、最通俗比喻(一辈子不忘)

  • app = 手机
  • setupRouter = 安装微信
  • setupStore = 安装支付宝
  • setupNaive = 安装主题
  • setupDirectives = 安装插件

ts

复制代码
安装微信(手机)
安装支付宝(手机)
安装主题(手机)

手机还是那个手机,只是一个个安装功能!


六、你只要记住 3 点

  1. 这些函数都是用来给 app 安装 "全局功能" 的
  2. 把 app 传进去 = 给这个 Vue 实例安装插件
  3. 目的:代码拆分、清爽、好维护

终极总结(你 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>  → 显示真正页面(首页/列表)

意思:

  1. 第一层:放整体布局(侧边栏 + 头部)
  2. 第二层:放内容页面

这就叫嵌套路由


五、默认插槽 vs 命名插槽:区别是什么?

表格

类型 写法 作用 使用频率
默认插槽 <router-view> 显示当前路由页面 ✅ 99%
命名插槽 <router-view name="xxx"> 一个路由同时显示多个组件 ❌ 极少

你只需要记住默认插槽!


六、最通俗比喻(一辈子不忘)

  • <router-view> = 电视机屏幕
  • 路由 = 换台
  • 页面组件 = 节目

换台(切换路由)→ 节目变了(页面变了)→ 屏幕没变(<router-view> 位置没变)


七、你只要记住 3 句

  1. <router-view> = 页面显示的位置
  2. 路由切换 → 页面从这里替换
  3. 只有默认插槽最常用,命名插槽几乎不用

终极总结(你 100% 懂了)

<router-view /> 是什么?

页面内容的容器、占位符、画框。

有什么插槽?

  • 默认插槽:显示页面(必须会)
  • 命名插槽:多面板同时显示(几乎不用)

作用?

路由切换页面,就是切换这里显示的内容!

一、为什么「组件通信」是超级重点?

Vue 是组件化开发 :页面拆成:父组件、子组件、孙组件、兄弟组件、跨层级组件、全局组件。组件都是独立隔离的,数据默认互不互通。

组件本身互不认识,想要互相传数据、调用方法,就必须靠「组件通信」。

你写的所有项目:

  • 弹窗传参
  • 列表点击修改
  • 侧边栏收缩
  • 全局提示、权限、菜单全靠 组件通信

二、Vue3 所有组件关系 & 对应通信方案(背这个就够)

1. 父子组件(最常用)

  • 父 → 子:props 传参
  • 子 → 父:defineEmits 自定义事件
  • 父拿子实例 / 方法:defineExpose + ref

2. 跨层级 / 深层祖孙(隔好几层)

  • provide / inject 依赖注入不用一层一层 props 透传,深层组件直接拿数据。

3. 兄弟组件 / 毫无关系的组件

  • Pinia(全局状态仓库) 首选
  • 小型简单项目:mitt 事件总线

4. 全局任意组件、跨实例通信(你前面问的双 app 实例)

  • 全局事件总线 mitt
  • 全局挂载 window 变量
  • Pinia 全局状态

三、难度 & 重要度排序

  1. ⭐⭐⭐⭐⭐ 必掌握props /emit/ Pinia

日常开发 90% 场景全靠这三个

  1. ⭐⭐⭐⭐ 高频provide /inject

后台管理、布局嵌套、深层组件必用

  1. ⭐⭐⭐ 了解即可mitt 事件总线、ref + expose、全局挂载

四、结合你当前项目举例

  1. 页面组件 给 弹窗组件传数据 → props
  2. 子组件关闭弹窗通知父组件 → emit
  3. 深层菜单、主题、全局配置 → provide/inject
  4. 所有组件共享用户信息、登录状态 → Pinia
  5. 主 app 和 独立全局弹窗 app 通信 → 事件总线

全覆盖,全部都是组件通信。


五、一句话总结

  1. 组件通信 Vue 核心重点,比基础语法重要得多;
  2. 层级越多、项目越大,越依赖通信方案;
  3. 面试必问:父子、兄弟、跨层级分别怎么传值;
  4. 你毕设、写项目、改 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(首选)

全局共享数据,所有组件都能读、改

  1. 定义仓库

ts

复制代码
// stores/user.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
  state: () => ({
    nickname: ''
  })
})
  1. 组件 A 修改数据

ts

复制代码
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
userStore.nickname = '张三'
  1. 组件 B 直接使用

ts

复制代码
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
console.log(userStore.nickname)

四、简单项目兄弟通信:mitt 事件总线

  1. 新建 utils/event.ts

ts

复制代码
import mitt from 'mitt'
export const emitter = mitt()
  1. 组件 A 发送事件

ts

复制代码
import { emitter } from '@/utils/event'
emitter.emit('refreshList', '参数')
  1. 组件 B 监听事件

ts

复制代码
import { emitter } from '@/utils/event'
emitter.on('refreshList', (val) => {
  console.log('收到:', val)
})

适合:轻量弹窗、跨页面简单通知


五、全局通信 / 多 Vue 实例通信(你项目的双 app)

ts

复制代码
// 挂载到window全局
window.$vue = app

// 任意地方直接使用
window.$vue.xxx

用来实现:主项目 ↔ 全局弹窗独立实例 互相通信


六、快速记忆总结(背诵版)

  1. 父子传值:props 下行,emit 上行
  2. 父操作子:ref + defineExpose
  3. 多层级嵌套:provide / inject
  4. 兄弟 / 全局共享:Pinia(首选)
  5. 简单跨组件通知:mitt 事件总线
  6. 跨独立 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>

  1. 代码更少,不用写冗余模板
  2. 性能更好,底层优化更强
  3. TS 支持更强,类型提示完美
  4. 开发更快,少写很多重复代码
  5. 未来趋势,旧写法逐渐淘汰

五、最通俗比喻

  • 旧写法 = 填写复杂表格,要填各种框、签名、盖章
  • 新写法 = 直接写字,想写啥写啥

六、超级记忆口诀(背会)

plaintext

复制代码
script setup 真正强,
不用 return 不用装,
组件直接引即用,
代码简洁不慌张!

终极总结(你 100% 懂了)

Vue3 最新写法 <script setup> 和旧写法的区别:

  1. 不用写 export default /defineComponent
  2. 不用写 setup () 函数
  3. 不用 return 暴露变量
  4. 组件不用注册,import 直接用
  5. 代码更少、性能更好、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 = 浅色油漆 / 深色油漆
  • 点击切换 = 换一桶油漆
  • 所有页面 = 墙壁

换一桶油漆 → 所有墙壁自动变成新颜色!


五、真实项目主题切换完整流程(你项目里就是这样)

  1. Pinia 存储当前主题 light/dark
  2. 点击按钮 → 切换主题
  3. 给 body 加 class = dark /light
  4. CSS 根据类名切换变量值
  5. 全站所有组件自动变色
  6. localStorage 保存主题,刷新不丢失

六、超级总结(背会这 3 句)

  1. 主题切换 = 切换 body 上的一个类名
  2. 所有颜色靠 CSS 变量统一管理
  3. 类名一变 → 全站自动变色

最终一句话

主题切换不是一个个改页面, 而是切换一个 "全局开关", 让所有 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?

  1. 代码不重复
  2. 好维护(改一个 Hook,全部生效)
  3. 组件更干净
  4. 逻辑清晰
  5. 方便团队协作

这就是你看到的:

ts

复制代码
const darkTheme = useDarkThemeHook()

六、Hook 的命名规则(必须记住)

所有 Hook 都以 use 开头

  • useDark
  • useTheme
  • useLang
  • useUserInfo
  • useTable
  • useModal

看到 useXxx() → 就是 Hook!


七、最通俗比喻(一辈子不忘)

  • Hook = 工具箱
  • 每个 Hook 是一个专用工具
  • 组件 = 工人
  • 工人不用造工具,直接拿工具箱使用

终极总结(你 100% 懂了)

Hook 是什么?

一个封装了 Vue 逻辑(状态、方法、计算属性...)的函数。

功能是什么?

  1. 复用逻辑
  2. 简化组件
  3. 分离代码
  4. 让项目更规范、更干净

一句话记住:

use 开头的函数,就是 Vue 组合式 Hook,用来封装功能!

相关推荐
恋猫de小郭1 小时前
Flutter 3.41.8 又双叒修复调试问题,草台班子日常 hotfix
android·前端·flutter
skywalk81631 小时前
lisp to 块编程 完全的中文编程思路:无空格编程
开发语言·lisp
liulian09161 小时前
【Flutter for OpenHarmony第三方库】Flutter for OpenHarmony 离线模式实现:让你的应用无网也能萌萌哒~
开发语言·flutter·华为·php·学习方法·harmonyos
南宫萧幕1 小时前
基于 DQN 与 Python-Simulink 联合仿真的 HEV 能量管理策略实战
开发语言·python·matlab·汽车·控制
iwS2o90XT1 小时前
Java多线程编程:Thread与Runnable的并发控制
java·开发语言·python
接着奏乐接着舞1 小时前
Cesium 自定义纹理
前端
鹤卿1231 小时前
Block基础
开发语言·ios·objective-c
鹏程十八少1 小时前
9. 2026金三银四 面试官问不垮:Java VS Android 设计模式 16 讲
前端·后端·面试