antd.pro中Modal中传值回调

昨天解决了一个问题,关于"怎么在解决Modal中嵌套Form表单时,表单项的校验和表单项值获取的问题",详见上一篇文章。

继续在昨天的基础上做开发任务,出现了另外一个问题,在API数据流过程中,怎么数据回传的问题。我的需求是在list页面中会有弹出框Modal,然后在Modal中会嵌入一个组件(比如创建和修改组件),当创建和修改组价表单提交时,经过数据交货,API调用,最后会返回response结果,然后根据response结果来控制Modal的显示/隐藏。因为创建成功,Modal应该消失,创建失败则消失。问题就出现在不知道怎么将response中的数据进行回传。

简单的说,从表单提交到数据返回,应该分为这么几个步骤:

  1. UI提交表单数据,通过dispatch方法发布action,同时会将参数传递给"namespace/interface"中
  2. 在module中会定义effects,根据1中的接口名找到对应的接口,然后发起service请求,就是调用services文件中的API接口。
  3. service中的接口去调用mock中定义服务(mock是antd.pro脚手架中umi提供的一种前后端分离,仅前端实现数据交互的过程)。通过异步的方式generator和yield来调用接口,最后response结果和reducer进行绑定,我们知道reducer是用于修改state状态值的。name数据就更新到state中了。
  4. 这个时候需要将response中的值返回给上层,即1中的dispatch,这样的话在提交表单的js中就能将操作结果(成功/失败)返回到上一层,即list页面。
  5. list页面获取到4返回response值,从而控制Modal的显示/隐藏。

下面就是怎么实现的问题了,由于初次接触antd.pro和umi这一套,所以碰到的问题不管大小都会作一下记录。

页面布局

这里先讲一下页面布局,知道布局,可能对后面的实现能更好的理解。

lsit.ts  UI页面

由于代码比较多,就不全部粘贴出来了,在说到具体的某一块的时候会再详细粘贴。

使用的是antd.pro提供的ProTable, 是一个几乎是table能涉及的功能,都涵盖全了。

然后在list页面引入了两个Modal,一个是创建,一个是修改,然后通过标志来控制显示/隐藏,拿创建来说吧,代码是这样的

        <CreateForm onCancel={() => {
          console.log("关闭")
          handleModalVisible(false)
        }} modalVisible={createModalVisible}
          onSubmit={async (value) => {
            console.log("value=",value)
            if (value === 0) {
              handleModalVisible(false);
              if (actionRef.current) {
                actionRef.current.reload();
              }
            }
          }}
        />

在里面模态框中帮我们定义了一些Header,content,footer比较简单。其中难点就是onCancle和onSubmit两个方法。

  • 取消的话是直接将模态框关闭,
  • 提交的话需要根据response中的code值去控制显示隐藏,成功隐藏,失败不隐藏。

CreateForm.ts   UI代码如下:

return (
    <Modal
      destroyOnClose
      title={intl.formatMessage({ id: 'home.user-center.newItem' })}
      visible={modalVisible}
      onCancel={onCancel}
      footer={[
        <Button key='submit' type="primary" htmlType="submit" onClick={onFinish}>
          {intl.formatMessage({ id: 'home.user-center.operation.save' })}
        </Button>,
        <Button key='cancel' htmlType="button" onClick={onCancel}>
          {intl.formatMessage({ id: 'home.user-center.operation.cancel' })}
        </Button>
      ]}
    >
      <Form {...layout} form={form} name="control-hooks">
        <Form.Item name="username"
          label={intl.formatMessage({ id: 'login.username' })}
          rules={[{ required: true, message: intl.formatMessage({ id: 'login.message.username' }) }]}>
          <Input />
        </Form.Item>
        <Form.Item name="apikey"
          label={intl.formatMessage({ id: 'home.user-center.apikey' })}
          rules={[{ required: true, message: intl.formatMessage({ id: 'home.user-center.message.apikey' }) }]}>
          <Input />
        </Form.Item>
        <Form.Item name="name"
          label={intl.formatMessage({ id: 'home.user-center.name' })}
          rules={[{ required: true, message: intl.formatMessage({ id: 'home.user-center.message.name' }) }]}>
          <Input />
        </Form.Item>
      </Form>
    </Modal>
  );

可以看出是一个简单的Modal,然后Modal中嵌套的是一个简单的表单。这就是简单的页面构建,了解了页面的结构,就具体说一下一步步是怎么实现的。

在将时下之前,先看一下项目的目录情况:

这是一个使用antd.pro脚手架搭建的一个标准的项目目录。

  • config  路由国际化配置,代理配置等
  • mock   模拟请求及数据
  • .umi     umi框架提供的一套服务
  • assets  静态资源
  • components  公共组件
  • e2e      测试
  • layouts  项目的布局,比如登录页,主页面等
  • locales   国际化
  • pages    模块视图
  • services  服务接口

只是一些主要的文件,其他的就不列出。进入正菜部分。

1、Modal显示

CreateForm.ts文件以下简称create.ts

const [createModalVisible, handleModalVisible] = useState<boolean>(false);

点击按钮通过handleModalVisible设置显示

create.ts

<Button type="primary" onClick={() => handleModalVisible(true)}>
    <PlusOutlined /> {intl.formatMessage({ id: 'home.user-center.operation.create' })}
</Button>

2.提交表单

由于在提交表单的时候,需要回传就需要通过callback函数回传,一般是都过props来获取onCancelonSubmit

create.ts

const { modalVisible, onCancel, onSubmit } = props;

点击提交按钮就会通过dispatch发送一个action

create.ts

  // 表单提交
  const onFinish = async () => {
    //用异步的方式去处理Modal中的表单校验和取值问题
    const values = await form.validateFields();
    const { dispatch } = props;
    // 发布事件
    dispatch({
      type: 'apisetting/addApiconfig',
      payload: { ...values },
    });    
  };

3.接收到action,调用service接口

根据dispatch中的type找到对应的函数,就会去对应的modules目录下的create.ts文件中的effects对应的函数

modules/create.ts

effects: {
        *addApiconfig({ payload, callback }, { call, put }) {
            console.log("payload", payload);
            const response = yield call(addApi, payload);
            yield put({
                type: 'add',
                payload: response,
            });
            // 请求成功
            if (response.errorCode === 0) {
                message.success(response.errorMessage);
            } else {
                message.error("Operation Faliure!");
            }

        },
}

使用的是generator和yield实现异步请求。调用services/create.ts中的接口,并把参数传递过去。

4.在service中调用服务端API

servcies/create.ts

import request from 'umi-request';

export async function addApi(params:object) {
    return request('/api/apisetting/add',{
        method: "POST",
        data: params
    });
}

5.在mock中配置API接口和数据

mock/create.ts

// eslint-disable-next-line import/no-extraneous-dependencies
import { Request, Response } from 'express';
import { parse } from 'url';
import { TableListItem, TableListParams } from '@/pages/AccountSettings/data';

// mock tableListDataSource
const getApiList = (current: number, pageSize: number) => {
  const tableListDataSource: TableListItem[] = [];
  tableListDataSource.push({
    key:'1',
    username:'chenqk',
    apikey:'12iuhi1b2gi859791h5iuh18y',
    name:'QCC1'
  })
  tableListDataSource.push({
    key:'2',
    username:'zhangsan',
    apikey:'678yugfjebbifew545',
    name:'QCC2'
  })
  tableListDataSource.push({
    key:'3',
    username:'lisi',
    apikey:'090firhgirh',
    name:'QCC3'
  })

  tableListDataSource.reverse();
  return tableListDataSource;
};

let tableListDataSource = getApiList(1, 100);

function list(req: Request, res: Response, u: string) {
  let realUrl = u;
  if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
    realUrl = req.url;
  }
  const { current = 1, pageSize = 10 } = req.query;
  const params = (parse(realUrl, true).query as unknown) as TableListParams;

  let dataSource = [...tableListDataSource].slice(
    ((current as number) - 1) * (pageSize as number),
    (current as number) * (pageSize as number),
  );
  const sorter = JSON.parse(params.sorter as any);
  if (sorter) {
    dataSource = dataSource.sort((prev, next) => {
      let sortNumber = 0;
      Object.keys(sorter).forEach((key) => {
        if (sorter[key] === 'descend') {
          if (prev[key] - next[key] > 0) {
            sortNumber += -1;
          } else {
            sortNumber += 1;
          }
          return;
        }
        if (prev[key] - next[key] > 0) {
          sortNumber += 1;
        } else {
          sortNumber += -1;
        }
      });
      return sortNumber;
    });
  }
  if (params.filter) {
    const filter = JSON.parse(params.filter as any) as {
      [key: string]: string[];
    };
    if (Object.keys(filter).length > 0) {
      dataSource = dataSource.filter((item) => {
        return Object.keys(filter).some((key) => {
          if (!filter[key]) {
            return true;
          }
          if (filter[key].includes(`${item[key]}`)) {
            return true;
          }
          return false;
        });
      });
    }
  }

  if (params.name) {
    dataSource = dataSource.filter((data) => data.name.includes(params.name || ''));
  }
  const result = {
    data: dataSource,
    total: tableListDataSource.length,
    success: true,
    pageSize,
    current: parseInt(`${params.currentPage}`, 10) || 1,
  };

  console.log("res.json(result)",res.json(result));
  return res.json(result);
}

function postRule(req: Request, res: Response, u: string, b: Request) {
  // alert("req="+req+',res='+res+',u='+u+',b='+b)
  let realUrl = u;
  if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
    realUrl = req.url;
  }

  const body = (b && b.body) || req.body;
  const { method, name, desc, key } = body;

  switch (method) {
    /* eslint no-case-declarations:0 */
    case 'delete':
      tableListDataSource = tableListDataSource.filter((item) => key.indexOf(item.key) === -1);
      break;
    case 'post':
      (() => {
        const i = Math.ceil(Math.random() * 10000);
        const newRule = {
          key: i,
          username: 'nick' + i,
          apikey: 'app8' + i,
          name:  'app6' + i
        };
        tableListDataSource.unshift(newRule);
        // res = {'erroCode':0}

        return res.json(newRule);
      })();
      return;

    case 'update':
      (() => {
        let newRule = {};
        tableListDataSource = tableListDataSource.map((item) => {
          if (item.key === key) {
            newRule = { ...item, desc, name };
            return { ...item, desc, name };
          }
          return item;
        });
        return res.json(newRule);
      })();
      return;
    default:
      break;
  }

  const result = {
    list: tableListDataSource,
    pagination: {
      total: tableListDataSource.length,
    },
  };

  res.json({errorCode:0,errorMessage:'Operation Successfully!'});
}

export default {
  'GET /api/apisetting/list': list,
  // 'POST /api/rule': postRule,
  'POST /api/apisetting/add ': postRule
};

由于后期需要根服务端对接接口,这里只是为了测试用的,测试整个项目的数据流向是否正确,所以我在最后简单的返回了一个errorCode和errorMessage,以便测试。

由于这是umi提供的,在保存后会自动编译,找不到对应的源码,好像是log都打不出来。

6.数据回溯

在1-5步中我们实现了从UI->module->service->mock的数据流向,那现在服务器收到了我们的请求,并且也response了,我们怎么将数据就行回传呢?

我们知道请求是在module的时候作为入口的,那么response应该是返回到module中。

*addApiconfig({ payload, callback }, { call, put }) {
            console.log("payload", payload);
            const response = yield call(addApi, payload);
            yield put({
                type: 'add',
                payload: response,
            });
            // 请求成功
            if (response.errorCode === 0) {
                message.success(response.errorMessage);
            } else {
                message.error("Operation Faliure!");
            }

        },
}

yield回来的数据,我们都存放到response中了,并且将请求回来的数据通过put的方式绑定到reducer的add中,从而去更新数据。

 reducers: {
        add(state, { payload }) {
            return {
                ...state,
                errorCode: payload.errorCode,
                list: payload.list
            };
        },
}

7.表单create.ts的dispatch回调函数

这个是我碰到的核心问题,问题倒是不难,就是初次使用的时候,没有好好的看API,导致遗漏。在提交表单的时候使用dispatch的方式去发布action,它还提供了一个callback回调函数,我想碰到回调函数就应该知道怎么处理了。

// 表单提交
  const onFinish = async () => {
    //用异步的方式去处理Modal中的表单校验和取值问题
    const values = await form.validateFields();
    const { dispatch } = props;
    // 发布事件
    dispatch({
      type: 'apisetting/addApiconfig',
      payload: { ...values },
      callback: response => {
        console.log("reponse=", response)
      }
    });    
  };

在dispatch中加上一个callback函数,即可在调用函数的时候通过callback将response结果回传过来。

8.在module中使用callback函数回传response

*addApiconfig({ payload, callback }, { call, put }) {
            console.log("payload", payload);
            const response = yield call(addApi, payload);
            yield put({
                type: 'add',
                payload: response,
            });
            // 请求成功
            if (response.errorCode === 0) {
                message.success(response.errorMessage);
            } else {
                message.error("Operation Faliure!");
            }
            callback(response)
        },

就是最后那行代码callback(response),可以将我们的请求结果返回到表单处。

9.通过callback函数将子组件的值传个父组件

list页面是父组件,create.ts表单为子组件。我们知道父组件往子组件传值通过props,子组件往父组件传值通过callback函数去传值。那只要调用一下onSubmit方法即可。

// 表单提交
  const onFinish = async () => {
    //用异步的方式去处理Modal中的表单校验和取值问题
    const values = await form.validateFields();
    const { dispatch } = props;
    // 发布事件
    dispatch({
      type: 'apisetting/addApiconfig',
      payload: { ...values },
      callback: response => {
        console.log("reponse=", response)
        onSubmit(response.errorCode)
      }
    });    
  };

10.在list页面接受回传值

        <CreateForm onCancel={() => {
          console.log("关闭")
          handleModalVisible(false)
        }} modalVisible={createModalVisible}
          onSubmit={async (value) => {
            console.log("value=",value)
            if (value === 0) {
              handleModalVisible(false);
              if (actionRef.current) {
                actionRef.current.reload();
              }
            }
          }}
        />

测试一下结果是不是能正常的返回。

  • payload是往接口传递的参数
  • response是接口返回的参数
  • value值是最终返回到list页面的值

这样,从UI请求到请求返回,这一套数据流向终于弄明白了。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值