1. 关于子组件提交数据的写法
你写的 onSubmit={props.onSubmit} 这种思路是对的,但在 Ant Design 的实际代码中,会稍微有一点点区别。
通常 Modal 组件本身没有 onSubmit 属性,它用的是 onOk。而且,子组件在提交时,通常需要先把自己的表单数据拿出来,再传给父组件。
常见的做法是:
<UpdateForm
// 1. 父传子:把控制弹窗显示的变量传过去
updateModalOpen={updateModalOpen}
// 2. 父传子:把当前要编辑的那一行数据传过去
values={currentRow || {}}
// 3. 子传父(回调):子组件点"提交"时,调用父组件定义的 handleUpdate 函数
onSubmit={async (value) => {
const success = await handleUpdate(value);
if (success) {
handleUpdateModalOpen(false); // 关闭弹窗
actionRef.current?.reload(); // 刷新表格
}
}}
// 4. 子传父(回调):子组件点"取消"时,让父组件关闭弹窗
onCancel={() => handleUpdateModalOpen(false)}
/>
const UpdateForm = (props) => {
const [form] = Form.useForm(); // 创建表单实例
const handleOk = async () => {
// 1. 校验表单并获取值
const values = await form.validateFields();
// 2. 调用父组件传过来的 onSubmit 逻辑
props.onSubmit(values);
};
return (
<Modal open={props.updateModalOpen} onOk={handleOk} onCancel={props.onCancel}>
<Form form={form} initialValues={props.values}>
{/* 表单项... */}
</Form>
</Modal>
);
};
2. 多层级通讯(爷爷传给孙子)
你提到的 Vue 里的 provide/inject,在 React 中对应的官方方案是 Context API。
方案 A:Context API(最常用)
这就像是在组件树上方架设了一个"广播站"。
- 创建上下文 :
const MyContext = createContext(); - 提供数据(爷爷辈) :使用
<MyContext.Provider value={...}>包裹子组件。 - 消费数据(孙子辈) :使用 useContext(MyContext) 钩子直接获取,不需要经过中间的爸爸组件。
方案 B:全局状态管理
如果你的项目非常大,或者很多页面都要共享这些数据(比如用户信息、主题颜色),通常会用:
- Redux / Zustand:把数据存在页面之外的一个"大仓库"里,谁要谁去取。
- UmiJS 插件 :既然你代码里有
@umijs/max,它内置了一个非常简单的全局状态管理------useModel。
-
- 在
src/models下写个文件。 - 任何组件只需要
const { initialState } = useModel('@@initialState');就能拿到数据。
- 在
- ●
3. 正常开发中常用的 Hooks 函数
在 React 的日常搬砖中,90% 的场景都在用下面这几个:
|-----------------|-------------------------------------------------|----------|
| Hook | 作用 | 直观比喻 |
| useState | 定义变量,改变它会触发页面刷新。 | 变量寄存器 |
| useEffect | 处理"副作用"。比如:页面刚加载时去调 API,或者某个变量变了我要跟着做点啥。 | 监听器/生命周期 |
| useRef | 1. 引用 DOM 元素或组件(如actionRef );2. 存一个不会触发刷新的变量。 | 记事本/钩子 |
| useMemo | 计算复杂数据并缓存,防止重复计算。 | 缓存器 |
| useCallback | 缓存函数,防止父组件刷新导致子组件跟着乱刷(性能优化)。 | 函数保险箱 |
| useContext | 跨级取数据(如上文所说)。 | 无线接收机 |
总结
- 父子通讯:靠 Props,子传父靠回调。
- 跨级通讯 :Vue 用
inject,React 用 useContext 或 useModel。 - 常用 Hooks :最最核心的是 useState(存数据)和 useEffect(发请求/监听)。
具体代码:
import { createContext } from 'react';
// 创建一个广播站,会创建不同的 Context 对象
export const DeptContext = createContext();
export const ThemeContext = createContext();
export const UserContext = createContext();
// 以上是MyContext 的文件
//爷爷组件(提供数据)
import { DeptContext } from './MyContext';
const UserPage = () => {
const [deptName, setDeptName] = useState('开发部');
return (
// 使用 Provider 包裹,value 里的东西,后代都能直接拿
<DeptContext.Provider value={deptName}>
<ParentComponent /> {/* 爸爸组件,它不需要接收 deptName 这个 prop */}
</DeptContext.Provider>
);
};
//孙子元素,直接接受:
//孙子组件拿数据时,必须明确说"我要听 DeptContext 频道":const deptName = useContext(DeptContext);它绝不会拿到 UserContext 里的数据
//因为你在 useContext 括号里传入的 Context 对象 是唯一的钥匙。
import { useContext } from 'react';
import { DeptContext } from './MyContext';
const GrandSon = () => {
// 孙子直接从广播站拿数据,不需要爸爸传话
const deptName = useContext(DeptContext);
return <div>当前部门是:{deptName}</div>;
};
useEffect 如何监听变化
在 React 里,没有专门的 watch****函数 ,所有的监听活儿都由 useEffect 承包了。
它是怎么监听的?
useEffect 接收两个参数:
- 回调函数:要做的事情。
- 依赖数组:你想监听谁,就把谁放进去。
- useEffect****vs Vue 的 watch
虽然它们都能监听数据,但有几个核心区别:
|-----------|-------------------------------|-------------------------------------------|
| 特性 | Vue 的 watch | React 的 useEffect |
| 触发时机 | 默认只在值改变时触发。 | 初始化渲染完成后一定会执行一次,然后才是值改变时触发。 |
| 新旧值对比 | 自动提供 (newValue, oldValue) 。 | 不提供旧值 。如果你非要旧值,得自己用 useRef 存一份手动对比。 |
| 写法 | 指定监听某个变量名。 | 将变量放入数组,支持同时监听多个(数组里任何一个变了都会触发)。 |
| 自动运行 | immediate: true 才在初次运行。 | 默认就是 immediate (总是先运行一遍)。 |
useEffect(() => {
console.log('部门 ID 变了,我要执行一些逻辑');
// 这里可以写你想做的操作,比如弹个窗
if (selectDeptId) {
fetchUserByDept(selectDeptId);
}
}, [selectDeptId]); // <--- 关键!这就是"监听器"。只要 selectDeptId 变了,上面的代码就会跑。
useEffect****可以写多个吗?
答案是:可以,而且非常推荐写多个!
在 React 开发中,我们提倡 "关注点分离" 。与其把所有逻辑都挤在一个巨大的 useEffect 里,不如按功能拆开:
关于监听 :多用 useEffect****没问题,但要记住, 别在 useEffect****里修改它自己监听的那个值 ,否则会陷入"无限死循环"(它变了 \(\rightarrow \) 触发监听 \(\rightarrow \) 你改了它 \(\rightarrow \) 它又变了 \(\rightarrow \) 又触发监听)
// 监听 1:专门负责根据部门 ID 加载数据
useEffect(() => {
if (deptId) {
loadTableData(deptId);
}
}, [deptId]);
// 监听 2:专门负责监听用户输入,实时验证名字格式
useEffect(() => {
if (userName.length > 10) {
message.warning('名字太长啦!');
}
}, [userName]);
// 监听 3:只在页面刚打开时执行一次(依赖数组为空)
useEffect(() => {
console.log('欢迎来到用户管理页面');
}, []);