今早学习笔记(父子组件通信 + Vuex 核心)
一、父子组件通信(企业级规范)
核心原则:父业务 + 子UI,单向数据流
核心逻辑:子组件只管表单渲染、交互,父组件只管业务、接口,不混乱、高复用
1. 子组件(表单/弹窗)职责
- 只负责:表单渲染、数据校验、弹窗显隐(自己管理状态)、收集表单数据
- 不负责:不写任何API请求、不处理业务逻辑
核心方法:
- open\(row\):供父组件调用,打开弹窗(支持新增/编辑,row为编辑数据)
- close\(\):自己关闭弹窗,重置表单
- submit\(\):表单校验通过后,通过 $emit 把数据抛给父组件
- 状态管理:弹窗isShow、表单formData,全部写在子组件data中,自己管理
2. 父组件(列表页)职责
- 负责:列表数据、接口请求(新增/编辑/删除)、页面刷新、成功/失败提示
核心操作:
- 通过 ref 调用子组件方法(open\(\)),打开弹窗
- 监听子组件 $emit 事件,接收表单数据,调用对应API
- API请求成功后,刷新列表,给出提示
3. 通信方式(标准写法)
- 父 → 子:ref 调用子组件方法(如:this.$refs.formRef.open())
- 子 → 父:$emit 抛数据(如:this\.$emit(\&\#39;submit\&\#39;, formData))
- 禁忌:子组件不能直接修改props,不能写API,父组件不插手子组件内部状态
4. 示例核心代码(回顾)
// 子组件:只抛数据,不调API
handleSubmit() {
this.$refs.form.validate(valid => {
if (valid) {
this.$emit('submit', this.formData); // 抛数据给父组件
this.close();
}
});
}
// 父组件:接收数据,调API
async handleSubmit(formData) {
try {
formData.id ? await updateApi(formData) : await addApi(formData);
this.$Message.success('操作成功');
this.getList(); // 刷新列表
} catch (e) {
this.$Message.error('操作失败');
}
}二、API交互规范(企业级)
核心原则:API 按“全局/页面”划分存放,不滥用
1. API 存放位置(关键区分)
| API类型 | 存放位置 | 典型例子 | 原因 |
|---|---|---|---|
| 全局公共API | Vuex 的 action 中 | 获取用户信息、权限、菜单、登录、登出 | 全局共享,调用后需更新Vuex全局状态,多页面复用 |
| 页面业务API | 父组件中 | 列表查询、新增/编辑用户、修改密码、删除 | 仅当前页面使用,数据只渲染当前页面,不共享 |
| 组件私有API | 子组件中(极少) | 仅子组件自身使用的极小接口 | 不影响其他组件,无需全局/父组件管理 |
2. 禁忌
- 禁止子组件写业务API(如新增、修改密码),影响复用性
- 禁止把页面私有API放进Vuex,导致store臃肿、混乱
- 禁止API调用后不处理异常(需加try/catch,给出提示)
三、Vuex 核心内容(重点中的重点)
核心定位:全局状态管理中心,解决跨组件共享数据、多层通信问题
1. Vuex 5大核心关键词(定义+作用+示例)
(1)State
- 定义:Vuex 的数据源,存储全局共享数据(唯一数据源)
- 通俗说:全局的 data,所有组件都能读取(不能直接修改)
示例:
`const state = \{userInfo: {}, // 全局用户信息
permissions: [] // 全局权限码
}`- 易错点:不能直接修改 state(如:this.$store.state.userInfo = {} 错误)
(2)Mutation
- 定义:唯一能修改 State 的地方,必须是同步函数(不能写异步)
- 作用:同步修改全局状态,接收 state 和 自定义参数
示例:
`const mutations = \{// state:Vuex自动传入;data:commit时传入的参数
SET\_USER\_INFO(state, data) {
state.userInfo = data; // 唯一正确修改state的方式
}
}`- 规则:只能同步,不能写API、定时器
(3)Action
- 定义:处理异步操作(API请求)、复杂业务逻辑,不直接修改state
- 作用:调API,拿到数据后,通过 commit 调用 Mutation 修改state
核心语法:第一个参数是 context 上下文对象,可解构简化
`// 解构写法(ES6,推荐)async getUserInfo({ commit }) {
const res = await getUserInfoApi(); // 异步调API
commit(\&\#39;SET\_USER\_INFO\&\#39;, res); // 调用mutation改state
}
// 不解构写法(兼容ES5)
async getUserInfo(context) {
const res = await getUserInfoApi();
context.commit(\&\#39;SET\_USER\_INFO\&\#39;, res); // 完整写法
}`
- 补充:Action 也能处理同步逻辑,还能通过 dispatch 调用其他Action
(4)Getter
- 定义:Vuex 的全局计算属性,对 state 数据进行加工、过滤
- 作用:派生数据,不修改原state,多组件复用相同计算逻辑
示例:
`// 全局getters\.jsconst getters = {
userId: state =\> state.user.userInfo.user\_id, // 派生用户ID
hasPerm: state =\> (permCode) =\> { // 派生权限判断方法
return state.user.permissions.includes(permCode);
}
}`
(5)Module
- 定义:Vuex 模块化拆分,按业务分模块管理(避免代码臃肿)
- 核心:必须开启 namespaced: true(命名空间),防止模块间命名冲突
示例(user模块):
`export default \{namespaced: true, // 开启命名空间(企业级必开)
state: {},
mutations: {},
actions: {}
}`- 模块合并:在 store/index.js 中导入所有子模块,合并到 modules 中
2. 关键调用关键词(commit / dispatch)
| 关键词 | 使用位置 | 调用对象 | 作用 | 示例 |
|---|---|---|---|---|
| commit | Action 内部(主要)、组件(极少) | Mutation | 触发同步修改state | commit(\&\#39;user/SET\_USER\_INFO\&\#39;, res) |
| dispatch | 组件(主要)、Action 内部 | Action | 触发异步操作/业务逻辑 | this.$store.dispatch(\&\#39;user/getUserInfo\&\#39;) |
3. 组件中使用 Vuex(map系列辅助函数)
- 核心原则:数据类(state、getters)放 computed;方法类(mutations、actions)放 methods
示例(完整写法):
`import \{ mapState, mapGetters, mapMutations, mapActions \} from \&\#39;vuex\&\#39;
export default {
computed: {
// 子模块state(带命名空间,必须写模块名)
\.\.\.mapState\(\&\#39;user\&\#39;, \[\&\#39;userInfo\&\#39;, \&\#39;permissions\&\#39;\]\),
// 全局getters(不用写模块名)
\.\.\.mapGetters\(\[\&\#39;userId\&\#39;, \&\#39;hasPerm\&\#39;\]\)},
methods: {
// 子模块mutations(带命名空间)
\.\.\.mapMutations\(\&\#39;user\&\#39;, \[\&\#39;SET\_USER\_INFO\&\#39;\]\),
// 子模块actions(带命名空间)
\.\.\.mapActions\(\&\#39;user\&\#39;, \[\&\#39;getUserInfo\&\#39;, \&\#39;logout\&\#39;\]\),
// 调用方式(映射后直接调用)
login\(\) \{
this\.getUserInfo\(\); // 等价于 this\.$store\.dispatch\(\&\#39;user/getUserInfo\&\#39;\)
this\.SET\_USER\_INFO\(data\); // 等价于 this\.$store\.commit\(\&\#39;user/SET\_USER\_INFO\&\#39;, data\)
\}}
}`
易错点:
- mapState/mapGetters 不能放 methods 里,反之亦然
- 带命名空间的模块,映射时必须写模块名(如 \&\#39;user\&\#39;)
4. 多参数传递(Vuex 官方规则)
- 规则:Mutation / Action 只能接收 1 个自定义参数
多参数解决方案:打包成一个对象传递
`// Action 中 commit(多参数打包)commit(\&\#39;SET\_USER\_INFO\&\#39;, {
userInfo: res,
test: \&\#39;测试数据\&\#39;
})
// Mutation 中接收(解构对象)
SET\_USER\_INFO(state, { userInfo, test }) {
state.userInfo = userInfo;
state.testData = test;
}`
5. 兼容写法(不支持ES6/ES2020)
| 现代语法(ES6+) | 兼容写法(ES5) |
|---|---|
| 解构 { commit } | context.commit(不解构,完整调用) |
| obj?.a?.b(可选链) | obj \&\& obj.a \&\& obj.b(逻辑与替代) |
| SET\_USER\_INFO(state, { a, b }) | SET\_USER\_INFO(state, payload) → const a = payload.a |
补充:实际开发中用 Babel 自动转译,不用手写兼容代码
6. Vuex 模块化目录结构(企业级标准)
src/store/
├── index.js # 入口文件,合并所有子模块、挂载Vuex
├── getters.js # 全局getter汇总
└── modules/ # 业务模块拆分
├── user.js # 用户模块(信息、权限)
├── permission.js # 权限模块(菜单、按钮权限)
└── layout.js # 布局模块(侧边栏、全屏)四、核心易错点汇总(重点记忆)
- 调用Action:this.$store\.dispatch\(\)(不是 $state,dispatch 不拼写错误)
- Mutation 只能同步,Action 可异步,不能搞反
- 修改state必须通过Mutation,不能直接修改
- Vuex模块必须开启 namespaced: true,否则会命名冲突
- map系列辅助函数:数据类放computed,方法类放methods
- 多参数必须打包成对象,Vuex不支持多个自定义参数
- API存放:全局公共API放Vuex,页面业务API放父组件
五、整体核心流程(串联所有知识点)
- 组件点击操作(如打开弹窗)→ 通过 ref 调用子组件 open() 方法
- 子组件完成表单交互、校验 → 通过 $emit 把数据抛给父组件
- 父组件接收数据 → 调用页面业务API(新增/编辑等)→ 提示+刷新列表
- 若涉及全局数据(如用户信息)→ 组件 dispatch 调用 Vuex 的 Action
- Action 调全局API → 拿到数据后 commit 调用 Mutation
- Mutation 同步修改 state → 组件通过 mapState/mapGetters 读取数据,视图更新
通用企业级设计思路:父业务 + 子 UI 表单弹窗模式
这套思路适用于所有后台管理系统:新增弹窗、编辑弹窗、表单抽屉、密码框、配置面板等,只要是「列表 + 表单弹窗」的结构,都能直接套用。
全程遵守:单向数据流、职责分离、高复用、无全局污染,是国内互联网公司后台系统的标准范式。
一、核心设计原则(4 条铁律)
子组件只做 UI,不碰业务
子组件 = 纯渲染组件:只负责表单、校验、弹窗开关、收集数据,绝不写接口。
父组件只做业务,不碰渲染细节
父组件 = 业务控制器:负责列表、接口请求、状态管理、刷新页面。
通信只走两条路
父 → 子:ref 调用方法
子 → 父:$emit 抛数据
谁的状态谁管理
弹窗显隐、表单数据,全部由子组件自己管理,父组件不插手内部状态。
二、通用职责划分(背会直接用)
🔹 子组件(Form 弹窗)负责:
弹窗显示 / 隐藏(isShow 写在自己 data)
表单数据(formData)
表单校验规则
打开 / 重置 / 关闭方法
表单校验通过后,把数据抛给父组件
不写任何 API、不请求后端
🔹 父组件(List 列表页)负责:
表格数据、分页、查询
点击按钮 → 通过 ref 打开子组件
接收子组件抛来的表单数据
调用新增 / 编辑 / 删除接口
接口成功 / 失败提示
刷新列表数据
三、通用开发步骤(万能流程)
- 拆分组件
一个业务弹窗 = 一个独立子组件
新增用户 → UserAddForm.vue
编辑密码 → LoginPwdForm.vue
配置参数 → ConfigForm.vue - 编写子组件(固定结构)
写弹窗(Modal/Drawer)
写表单 + 校验规则
data 里定义:isShow、formData、loading
写公共方法:open(row)、close()、submit()
校验通过后 $emit('submit', formData) - 父组件引入并挂载
import 子组件
注册 components
模板中放入子组件,绑定 ref 和 @submit
点击按钮调用 this.$refs.xxx.open() - 父组件处理业务逻辑
接收子组件数据
调用对应 API
成功提示 + 刷新列表
失败提示
评论