--- name: jjb-loading-reset description: 定义 JJB Loading 状态重置规范。在使用 resetModelState、弹窗/抽屉关闭或 confirmLoading 时使用。 --- # 接口与数据层规范 ## Loading 状态重置规范 > **⚠️ 重要约束**:当 `declareRequest` 定义的接口 Action 的 loading 状态(如 `loading`、`confirmLoading`、`submitLoading` 等)在可关闭组件或页面中使用时,**必须在组件销毁或关闭时调用 `resetModelState` 重置对应的 loading 字段**。 ## 问题说明 `declareRequest` 定义的接口 Action 会自动管理 loading 状态(如 `loading`、`confirmLoading`、`submitLoading` 等)。但在以下场景中,loading 状态可能无法自动重置: - 接口请求异常中断 - 弹窗/抽屉在请求过程中被关闭 - 组件卸载时请求仍在进行 - 页面跳转时请求仍在进行 这会导致 loading 状态一直保持为 `true`,下次打开组件或进入页面时仍然显示加载状态,影响用户体验。 ## 适用场景 以下场景需要特别注意重置 loading 状态: 1. **弹窗(Modal)**:弹窗关闭时 2. **抽屉(Drawer)**:抽屉关闭时 3. **页面组件**:页面卸载时 4. **条件渲染组件**:组件被条件隐藏时 5. **路由切换**:路由跳转时 ## 解决方案 在组件销毁或关闭时,调用 `resetModelState` 重置对应的 loading 字段。 ## 使用示例 ### ✅ 场景一:弹窗中使用 confirmLoading ```javascript import { useContext, useState, useEffect } from 'react'; import { Modal, Form, Input, Button } from 'antd'; import { Connect } from '@cqsjjb/jjb-dva-runtime'; import { InjectContext } from '~/enumerate/context'; import { NS_USER } from '~/enumerate/namespace'; function UserList({ createUserAction, resetModelState }) { const context = useContext(InjectContext); const [form] = Form.useForm(); const [modalOpen, setModalOpen] = useState(false); // ✅ 推荐:使用 useEffect 监听弹窗状态,关闭时重置 loading useEffect(() => { if (!modalOpen) { resetModelState(NS_USER, { confirmLoading: false }); } }, [modalOpen]); const handleSubmit = async (values) => { const res = await createUserAction(values); if (res.success) { context.message.success('创建成功!'); setModalOpen(false); } }; return ( <> setModalOpen(false)} footer={[ , ]} >
); } export default Connect([NS_USER], true)(UserList); ``` ### ✅ 场景二:抽屉中使用 submitLoading ```javascript import { useState, useEffect } from 'react'; import { Drawer, Form, Button } from 'antd'; import { Connect } from '@cqsjjb/jjb-dva-runtime'; import { NS_ORDER } from '~/enumerate/namespace'; function OrderDrawer({ updateOrderAction, resetModelState, drawerOpen, onClose }) { const [form] = Form.useForm(); // ✅ 抽屉关闭时重置 loading 状态 useEffect(() => { if (!drawerOpen) { resetModelState(NS_ORDER, { submitLoading: false }); } }, [drawerOpen]); const handleSubmit = async (values) => { const res = await updateOrderAction(values); if (res.success) { onClose(); } }; return ( form.submit()} > 提交 } >
{/* 表单内容 */}
); } export default Connect([NS_ORDER], true)(OrderDrawer); ``` ### ✅ 场景三:页面组件中使用 loading ```javascript import { useEffect } from 'react'; import { Connect } from '@cqsjjb/jjb-dva-runtime'; import { NS_PRODUCT } from '~/enumerate/namespace'; function ProductDetail({ product, fetchProductAction, resetModelState, productId }) { useEffect(() => { if (productId) { fetchProductAction({ id: productId }); } // ✅ 组件卸载时重置 loading 状态 return () => { resetModelState(NS_PRODUCT, { loading: false }); }; }, [productId]); if (product?.loading) { return
加载中...
; } return
{product?.data?.name}
; } export default Connect([NS_PRODUCT], true)(ProductDetail); ``` ### ✅ 场景四:条件渲染组件中使用 loading ```javascript import { useState, useEffect } from 'react'; import { Connect } from '@cqsjjb/jjb-dva-runtime'; import { NS_USER } from '~/enumerate/namespace'; function UserForm({ createUserAction, resetModelState, visible }) { const [form] = Form.useForm(); // ✅ 组件隐藏时重置 loading 状态 useEffect(() => { if (!visible) { resetModelState(NS_USER, { confirmLoading: false }); } }, [visible]); if (!visible) { return null; } return (
createUserAction(values)}> {/* 表单内容 */}
); } export default Connect([NS_USER], true)(UserForm); ``` ### ✅ 场景五:路由跳转时重置 loading ```javascript import { useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { Connect } from '@cqsjjb/jjb-dva-runtime'; import { NS_ORDER } from '~/enumerate/namespace'; function OrderList({ orderList, fetchOrderListAction, resetModelState }) { const location = useLocation(); useEffect(() => { fetchOrderListAction(); }, []); // ✅ 路由切换时重置 loading 状态 useEffect(() => { return () => { resetModelState(NS_ORDER, { loading: false, confirmLoading: false }); }; }, [location.pathname]); return (
{/* 列表内容 */}
); } export default Connect([NS_ORDER], true)(OrderList); ``` ## 核心原则 1. **必须重置**:当 loading 状态来源于 `declareRequest` 时,组件销毁或关闭时必须重置 2. **重置时机**: - 弹窗/抽屉:在 `onCancel`、`afterClose` 或 `useEffect` 中监听关闭状态 - 页面组件:在 `useEffect` 清理函数中重置 - 条件渲染:在 `useEffect` 中监听显示状态 - 路由切换:在 `useEffect` 清理函数中重置 3. **重置字段**:根据 `declareRequest` 中定义的 loading 字段名进行重置(如 `loading`、`confirmLoading`、`submitLoading` 等) 4. **命名空间**:使用正确的命名空间常量(如 `NS_USER`)调用 `resetModelState` 5. **批量重置**:可以一次性重置多个 loading 字段 ## 批量重置示例 ```javascript // ✅ 一次性重置多个 loading 字段 useEffect(() => { if (!modalOpen) { resetModelState(NS_USER, { loading: false, confirmLoading: false, submitLoading: false }); } }, [modalOpen]); ``` ## 相关规范 - 关于 `resetModelState` 的详细用法,请参考 [`命名空间与视图连接`](../03-命名空间与视图连接/SKILL.md) - 关于 `declareRequest` 的 loading 状态定义,请参考 [`接口定义规范`](../02-接口定义规范/SKILL.md)