SmartAdmin–VUE3
1.对象
1. 响应式对象reactive
reactive是 Vue 3 核心 API,作用是把普通对象 / 数组转换成「响应式代理对象」:
- 响应式的核心特性:当对象的属性(如
queryForm.keywords)发生变化时,Vue 能自动检测到变化,并更新页面上绑定该属性的视图(比如输入框内容、分页组件);- 如果直接用普通对象,修改字段后页面不会自动更新,这也是 Vue 做响应式的核心目的。
1 | const queryFormState = { |
初始状态定义:
queryFormState是一个普通对象,存放查询表单的默认值(如分页、时间范围、筛选条件),作为表单的 “重置基准”;不推荐const queryForm = reactive({ queryFormState});,因为这样会污染模板,修改响应式对象时,模板也会被同步修改。响应式转换:
reactive({ ...queryFormState })通过浅拷贝把初始状态转换成 Vue 3 的响应式对象queryForm,保证表单字段变化时触发视图更新;同时将queryFormState进行浅拷贝后进行复制,有利于保护原模板对象不受影响,重置表单时,方便进行初始化。浅拷贝的坑(嵌套对象场景)
3.1 如果queryFormState包含嵌套对象(比如timeRange: { begin: null, end: null }),{ ... }浅拷贝会导致嵌套层级失去响应式。
1 | // 问题复现 |
3.2 浅拷贝({ ... })只会拷贝第一层字段,嵌套的 timeRange 本质是 “引用地址”,所以 queryForm.timeRange 和 queryFormState.timeRange 指向同一个对象:
1 | // 修改响应式对象的嵌套字段 |
3.3 后续重置模板的时候,模板不干净,会有问题。
1 | // 尝试重置:把模板赋值给响应式对象 |
原因:Object.assign 也是浅拷贝,只会替换第一层字段的 “值”,而 timeRange 是引用类型,模板里的 timeRange 和响应式对象里的 timeRange 是同一个对象,所以嵌套属性不会被覆盖。
示例代码中都是单层字段,暂时无问题,
1 | // 深拷贝生成响应式对象(避免浅拷贝问题) |
toRaw是什么?
toRaw是 Vue 3 提供的 API,作用是获取响应式对象的 “原始普通对象”(解除响应式代理)。
- 语法:
toRaw(响应式对象)→ 返回该对象的原始普通版本;- 核心价值:避免操作响应式对象时的 “代理干扰”,尤其在深拷贝 / 数据传递时,用原始对象更安全。
- 通过
toRaw获取的原始对象和响应式对象是 “同一个本体” —— 修改原始对象的属性,响应式对象会自动同步
封装的重置方法
1 | /** |
reactive 数组直接赋值会丢失响应式,需要用 Array.splice 等方法;
比较麻烦,而ref可以直接.value进行赋值,相对简单一些。
1.2 ref
在模板中组件中声明ref="alarmFormDrawer" ,在脚本里 const alarmFormDrawer = ref() 的变量名完全一致时,Vue 会自动把该组件的实例对象绑定到这个 ref 变量上,可以通过 alarmFormDrawer.value 直接操作这个组件。
2. APIS
2.1 axios拦截器
1 | const res = response.data; |
3. table
3.1 table columes
1 | const tableColumns = ref([ |
dataIndex高阶用法
1 | 1. 匹配嵌套对象的字段 |
3.2 插槽
bodyCell:个性化单元格 v-slot:bodyCell=”{text, record, index, column}”
#bodyCell 是 AntD Vue 表格组件暴露的单元格自定义插槽(具名作用域插槽),作用是:自定义表格中「指定列」的单元格渲染内容(比如把纯文本改成链接、按钮、标签等)。
它会向父组件传递 3 个核心参数(你可以理解为 “表格给插槽的数据源”):
| 参数 | 含义 |
|---|---|
column |
当前单元格对应的列配置(比如 column.dataIndex 是列的 “数据索引”,对应 tableColumns 里的 dataIndex) |
record |
当前单元格所在行的完整数据(比如 record.noticeId 是当前行的公告 ID) |
text |
当前单元格的原始文本值(即 record[column.dataIndex] 的值,比如列的 dataIndex 是 title,则 text = record.title) |
4. button
1 | <a-button type="primary" > |
2.插槽
2.1 默认插槽-匿名插槽
组件内部只留一个「通用占位符」,使用者直接在组件标签内写内容即可,无需指定插槽名。
1 | 开发者 |
1 | 使用者 |
2.2 具名插槽
组件内部有多个占位符:
1 | 开发者 |
1 | 使用者 |
2.3 作用域插槽
「组件内部的数据,能被插槽内容访问和自定义渲染」
- 普通插槽(默认 / 具名):只能从「父组件向子组件传内容」,子组件的数据无法反向给插槽用;
- 作用域插槽:子组件把内部数据「传递给插槽」,父组件在插槽中接收并自定义渲染逻辑。
1 | 开发者 |
1 | 调用者 |
插槽使用小技巧
开发者可以在插槽内设置默认值。
1
2
3
4
5<!-- a-button.vue 内部 -->
<slot name="icon">
<!-- 默认图标:使用者没传 #icon 时显示 -->
<DefaultIcon />
</slot>解构插槽参数
1 | <template #item="{ data: user }"> |
2.3 插槽的使用:
插槽的写法看似多,核心就3 类核心场景
| 插槽类型 | 核心特点 | 典型写法 |
|---|---|---|
| 具名插槽 | 有名字,精准插位置 | <template #icon> |
| 作用域插槽 | 子传数据,父自定义渲染 | <template #item="{ user }"> |
| 默认插槽 | 无名字,通用占位符 | <template #default="{ user }"> |
2.3.1 具名插槽-无参数:只塞内容,没有数据
#icon = v-slot:icon(简写),icon 是组件定义的插槽名;
1 | <a-button> |
2.3.2 作用域插槽:具名+接收参数
| 写法 | 本质 | 适用场景 |
|---|---|---|
<template #item="slotProps"> |
接收完整参数对象 | 参数多,需要保留所有数据 |
<template #item="{ user }"> |
解构参数对象,提取需要的字段 | 只需要部分参数(推荐) |
<template #item="{ data: user }"> |
解构 + 重命名参数 | 参数名不语义化,重命名 |
原始写法:<template #item="slotProps">:把「子组件传递的所有数据」打包成 slotProps 对象;
slotProps 这个参数不固定,写成abc都可以
1 | <UserList> |
简化写法:<template #item="{ user }">:解构 slotProps,直接提取 user 字段,少写一层嵌套;
1 | <UserList> |
重命名写法:<template #item="{ data: user }">:子组件传递的参数名是 data,但你觉得 user 更语义化,就重命名,解决参数名不直观、和父组件变量冲突的问题。
1 | <!-- 子组件传递的参数名是data --> |
<template #operation="{ record }">:operation是插槽名,record是结构后的参数。
2.3.3 默认作用域插槽:默认插槽+数据
<template #default="{ user }">:组件只留了一个插槽(无名字),但需要子组件传数据(比如简单的列表组件)。:
#default 就是 “默认插槽” 的名字(可以省略不写);本质是「默认插槽 + 作用域插槽」的组合。
1 | <!-- 完整写法:#default --> |
2.4 快速判断用哪种写法的步骤
- 第一步:看组件有没有给插槽命名
- 有名字(比如
icon/item/operation)→ 用#名字; - 没名字 → 用
#default(或省略)。
- 有名字(比如
- 第二步:看是否需要子组件传数据
- 不需要 → 直接写
<template #名字>内容</template>; - 需要 → 加参数:
#名字="参数对象"(或解构#名字="{ 字段 }")。
- 不需要 → 直接写
3.组件数据传递
3.1 defineExpose
defineExpose 是 Vue 3 <script setup> 语法中专门用来向外暴露子组件内部方法 / 属性的 API,核心作用是:让父组件能通过 ref 访问子组件里的方法 / 数据(如果不写 defineExpose,父组件拿不到子组件的任何内部内容)。
1 | <!-- 子组件:AlarmFormDrawer.vue --> |
1 | <!-- 父组件 --> |
核心作用拆解
| 场景 | 无 defineExpose |
有 defineExpose |
|---|---|---|
父组件通过 ref 访问子组件 |
alarmFormDrawer.value 是空对象(拿不到任何方法 / 属性) |
alarmFormDrawer.value 能拿到暴露的 showModal 方法 |
| 子组件内部逻辑 | 仅自己可用,对外封闭 | 对外开放指定方法 |
defineExpose 的核心价值是:打破 <script setup> 的封装性,按需向父组件开放子组件的内部能力。
- 子组件:用
defineExpose列出要 “对外公开” 的方法 / 属性; - 父组件:通过
ref拿到子组件实例后,用.value.暴露的名称调用 / 访问; - 没有它,父组件无法通过
ref操作子组件的任何内部逻辑(这是 Vue 3 为了组件封装性做的设计)。
9. js
9.1 json/map
在 JavaScript 中,JSON(本质是字符串格式)和 Map(ES6 新增的键值对数据结构)是完全不同的概念。
| 维度 | JSON(JSON 字符串) | Map(ES6 键值对集合) |
|---|---|---|
| 本质 | 文本格式的字符串(用于数据传输 / 存储) | 内存中的键值对数据结构(用于代码内数据处理) |
| 键类型限制 | 键必须是字符串(且需双引号包裹) | 键支持任意类型(字符串、数字、对象、布尔等) |
| 值类型限制 | 仅支持字符串、数字、布尔、数组、普通对象、null(不支持函数、undefined) | 支持任意 JS 类型(函数、对象、undefined 等) |
| 可直接修改? | 否(需先解析为 JS 对象) | 是(原生方法直接增删改查) |
9.1.1 初始化方法
JSON
① 手动编写 JSON 字符串(严格格式);
1 | const jsonStr = '{"name":"张三","age":25,"hobbies":["篮球","游戏"]}'; |
② JS 对象 / 数组 → JSON 字符串:JSON.stringify()
1 | const userObj = {name: "张三", age: 25}; |
MAP
① 空 Map:new Map();
1 | const map1 = new Map (); |
② 键值对数组初始化:new Map([[key, value], ...]);
1 | const map2 = new Map ([ |
③ JS 对象转 Map:new Map(Object.entries(对象))
1 | const userObj = {name: "张三", age: 25}; |
9.1.2. 读取/查询
| 操作 | JSON 操作方法 | Map 操作方法 |
|---|---|---|
| 语法/方法 | ① JSON 字符串 → JS 对象:JSON.parse();② 按 JS 对象方式读取( . / []) |
① 读取指定键:map.get(键);② 判断键是否存在: map.has(键);③ 获取长度: map.size |
| 示例代码 | const jsonStr = ‘{“name”:”张三”,”age”:25}’; const userObj = JSON.parse(jsonStr);//必选 console.log(userObj.name); // 张三(点语法) console.log(userObj[“age”]); // 25(方括号语法) console.log(userObj.gender); // undefined(不存在的键) |
const map = new Map([[“name”, “张三”], [123, “数字键”]]); console.log(map.get(“name”)); // 张三 console.log(map.get(123)); // 数字键 console.log(map.get(“gender”)); // undefined(不存在的键) console.log(map.has(“name”)); // trueconsole.log(map.size); // 2 |
| 新增/修改 | let jsonStr = ‘{“name”:”张三”,”age”:25}’; // 1. 解析为对象 let userObj = JSON.parse(jsonStr); // 2. 修改/新增属性 userObj.age = 26; // 修改已有键 userObj.gender = “男”; // 新增键 // 3. 重新转为 JSON 字符串(必选步骤) jsonStr = JSON.stringify(userObj); console.log(jsonStr); // {“name”:”张三”,”age”:26,”gender”:”男”} |
const map = new Map([[“name”, “张三”]]); // 修改已有键 map.set(“name”, “李四”); // 新增数字键 map.set(123, “数字值”); // 新增对象键 map.set({ id: 1 }, “对象值”); console.log(map.get(“name”)); // 李四 console.log(map.size); // 3 |
| 删除 | let jsonStr = ‘{“name”:”张三”,”age”:25}’; let userObj = JSON.parse(jsonStr); // 1. 删除对象属性 delete userObj.age; // 2. 重新转 JSON jsonStr = JSON.stringify(userObj); console.log(jsonStr); // {“name”:”张三”} |
const map = new Map([[“name”, “张三”], [123, “数字键”]]); // 1. 删除指定键 map.delete(123); // 返回 true(删除成功) map.delete(“gender”); // 返回 false(键不存在) // 2. 清空所有键值对 map.clear(); console.log(map.size); // 0 |
| 遍历 | const jsonStr = ‘{“name”:”张三”,”age”:25}’; const userObj = JSON.parse(jsonStr); // 1. 获取所有键 const keys = Object.keys(userObj); // [“name”, “age”] // 2. 获取所有值 const values = Object.values(userObj); // [“张三”, 25] // 3. 遍历键值对 Object.entries(userObj).forEach(([key, value]) => { console.log(key, value); // name 张三;age 25 }); |
const map = new Map([[“name”, “张三”], [123, “数字键”]]); // 1. 获取所有键(转数组) const keys = […map.keys()]; // [“name”, 123] // 2. 获取所有值(转数组) const values = […map.values()]; // [“张三”, “数字键”] // 3. for…of 遍历 for (const [key, value] of map) { console.log(key, value); } // 4. forEach 遍历 map.forEach((value, key) => { console.log(key, value); // 注意:回调参数是 (value, key) }); |
| 试用场景 | 接口数据传输(前后端/跨端) 本地存储(localStorage) |
9.2 undefind/null区别
在 JavaScript 中,undefined 和 null 都表示 “空值”,但设计初衷、语义、使用场景完全不同,声明 / 赋值方式也有明确规范,下面用「核心区别 + 声明方式 + 实战场景」讲透:
核心区别
| 维度 | undefined | null |
|---|---|---|
| 语义 | 表示 “未定义”:变量已声明但未赋值,或属性 / 方法不存在 | 表示 “空值”:主动赋值为 “无 / 空”,代表 “有定义但值为空” |
| 类型(typeof) | typeof undefined → "undefined" |
typeof null → "object"(历史 bug,本质是原始值) |
| 出现方式 | 1. 变量声明未赋值;2. 函数无返回值;3. 访问不存在的对象属性 / 数组项;4. 函数参数未传递 | 1. 主动赋值(如 let a = null);2. 表示 “空引用”(如 DOM 查询无结果 document.getElementById('xxx') → null) |
| 相等性(==) | undefined == null → true(值相等) |
同上 |
| 严格相等(===) | undefined === null → false(类型不同) |
同上 |
声明/赋值方式
undefined:无需主动声明,由 JS 自动赋值
undefined 是 JS 内置的原始值,不建议手动赋值(语义混乱),通常由 JS 自动产生:
1 | // 场景1:变量声明但未赋值 → 自动为 undefined |
null:必须主动赋值,代表 “显式空值”
null 是 “程序员主动标记的空”,只有手动赋值才会出现,是规范的 “空值声明方式”:
1 | // 场景1:声明变量时,明确表示“初始为空” |
1 | // 方式1:利用 ==(简洁) |