safety-eval-service-frontend/.cursor/skills/specs/11-接口与数据层规范/08-Loading状态重置规范/SKILL.md

7.8 KiB
Raw Blame History

name description
jjb-loading-reset 定义 JJB Loading 状态重置规范。在使用 resetModelState、弹窗/抽屉关闭或 confirmLoading 时使用。

接口与数据层规范

Loading 状态重置规范

⚠️ 重要约束:当 declareRequest 定义的接口 Action 的 loading 状态(如 loadingconfirmLoadingsubmitLoading 等)在可关闭组件或页面中使用时,必须在组件销毁或关闭时调用 resetModelState 重置对应的 loading 字段

问题说明

declareRequest 定义的接口 Action 会自动管理 loading 状态(如 loadingconfirmLoadingsubmitLoading。但在以下场景中loading 状态可能无法自动重置:

  • 接口请求异常中断
  • 弹窗/抽屉在请求过程中被关闭
  • 组件卸载时请求仍在进行
  • 页面跳转时请求仍在进行

这会导致 loading 状态一直保持为 true,下次打开组件或进入页面时仍然显示加载状态,影响用户体验。

适用场景

以下场景需要特别注意重置 loading 状态:

  1. 弹窗Modal:弹窗关闭时
  2. 抽屉Drawer:抽屉关闭时
  3. 页面组件:页面卸载时
  4. 条件渲染组件:组件被条件隐藏时
  5. 路由切换:路由跳转时

解决方案

在组件销毁或关闭时,调用 resetModelState 重置对应的 loading 字段。

使用示例

场景一:弹窗中使用 confirmLoading

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 (
    <>
      <button onClick={() => setModalOpen(true)}>新增用户</button>
      <Modal
        open={modalOpen}
        title="新增用户"
        onCancel={() => setModalOpen(false)}
        footer={[
          <Button key="cancel" onClick={() => setModalOpen(false)}>
            取消
          </Button>,
          <Button
            key="submit"
            type="primary"
            loading={createUserAction?.confirmLoading}
            onClick={() => form.submit()}
          >
            确定
          </Button>
        ]}
      >
        <Form form={form} layout="vertical" onFinish={handleSubmit}>
          <Form.Item name="name" label="用户名">
            <Input placeholder="请输入用户名" allowClear />
          </Form.Item>
        </Form>
      </Modal>
    </>
  );
}

export default Connect([NS_USER], true)(UserList);

场景二:抽屉中使用 submitLoading

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 (
    <Drawer
      open={drawerOpen}
      onClose={onClose}
      footer={
        <Button
          type="primary"
          loading={updateOrderAction?.submitLoading}
          onClick={() => form.submit()}
        >
          提交
        </Button>
      }
    >
      <Form form={form} onFinish={handleSubmit}>
        {/* 表单内容 */}
      </Form>
    </Drawer>
  );
}

export default Connect([NS_ORDER], true)(OrderDrawer);

场景三:页面组件中使用 loading

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 <div>加载中...</div>;
  }

  return <div>{product?.data?.name}</div>;
}

export default Connect([NS_PRODUCT], true)(ProductDetail);

场景四:条件渲染组件中使用 loading

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 (
    <Form form={form} onFinish={(values) => createUserAction(values)}>
      {/* 表单内容 */}
      <Button
        type="primary"
        htmlType="submit"
        loading={createUserAction?.confirmLoading}
      >
        提交
      </Button>
    </Form>
  );
}

export default Connect([NS_USER], true)(UserForm);

场景五:路由跳转时重置 loading

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 (
    <div>
      {/* 列表内容 */}
    </div>
  );
}

export default Connect([NS_ORDER], true)(OrderList);

核心原则

  1. 必须重置:当 loading 状态来源于 declareRequest 时,组件销毁或关闭时必须重置
  2. 重置时机
    • 弹窗/抽屉:在 onCancelafterCloseuseEffect 中监听关闭状态
    • 页面组件:在 useEffect 清理函数中重置
    • 条件渲染:在 useEffect 中监听显示状态
    • 路由切换:在 useEffect 清理函数中重置
  3. 重置字段:根据 declareRequest 中定义的 loading 字段名进行重置(如 loadingconfirmLoadingsubmitLoading 等)
  4. 命名空间:使用正确的命名空间常量(如 NS_USER)调用 resetModelState
  5. 批量重置:可以一次性重置多个 loading 字段

批量重置示例

// ✅ 一次性重置多个 loading 字段
useEffect(() => {
  if (!modalOpen) {
    resetModelState(NS_USER, {
      loading: false,
      confirmLoading: false,
      submitLoading: false
    });
  }
}, [modalOpen]);

相关规范