Mock平台-08开发:项目管理(四)编辑功能和Component抽离

Mock平台头图8.png

【Mock平台】为系列测试开发教程,从0到1编码带你一步步使用Spring Boot 和 Antd React框架完成搭建一个测试工具平台,希望作为一个实战项目对各位的测试开发学习之路有帮助,欢迎关注《大奇测试开发》公众号、博客原创渠道获取最佳阅读,关于大奇一位专注测试技术实践原创与好文分享的的家伙。

本篇重点:继续基于项目增加所有用到的Form和Modal两个组件做一些深化,实现项目管理的编辑功能,并将代码进行合并优化,提取第一个自定义的component组件。

1.项目编辑功能

要实现项目编辑对话框,需要拿到对应行的项目详细信息,可以通过在点击行按钮时候将整行信息传入,即之前table配置列信息时候操作列 render: (text, record) => ()中的record数据,但如果你返回的列表不不是所有字段,那就需要拿到行对应ID,通过查询接口拿到详细信息保存到一个定义好的变量中。

我们这里项目信息比较简单,所以采用前者,定义一个详细信息变量、编辑点击事件,并注意需要将之前定义在外部projectColumns挪到Project(...)主体内,让其正确调用内部新定义的方法。

const Project = () => {
  // 迁移列到内部,并添加editAction方法透传record
  const projectColumns = [
      {dataIndex:"id",title:"编号",},
      ...省略...
      {dataIndex:"option",title:"操作",
        render: (text, record) => (
          <Space>
            <a onClick={()=>editAction(record)}>编辑</a>
            <a>删除</a>
          </Space>
        ),
      },
    ]  
    
  // 定义项目编辑操作,并动态赋值到新的详细项目变量
  const [projectInfo, setProjectInfo] = useState({});
  const editAction = (record) => {
    setProjectInfo(record);
  }
}
  

在实现编辑对话框表单之前需要了解如何初始化数据,从 Form 组件官方API可以查看到其initialValues 可以绑定初始信息,值与name直接匹配,也可以在Form.Item 中initialValue独立绑定,好处是可以做些计算、转换再初始化。

参考上篇添加逻辑,拷贝一份Modal+Form修改其文案,并添加一个ID项(禁止编辑状态),再做个整体数据绑定操作,其中先不做保存修改操作,一份去了重的代码参考如下:

  // 控制显示编辑项目对话框表单
  const [projectEditVisible, setProjectEditVisible] = useState(false);

... 省略部分...
    <Modal
        id="p_edit"
        title="项目修改"
        visible={projectEditVisible}
        destroyOnClose="true"
        onCancel={()=>setProjectEditVisible(false)}
        onOk={() => {
           // 暂时不实现接口操作,先看交互是否满足
        }}
      >
        <Form 
          initialValues={projectInfo}
        >
          <Form.Item name='id' label='编号'>
            <Input disabled></Input>
          </Form.Item>
          <Form.Item
            name='name'
            label='名称'
            rules={[
              {
                required: true,
                message: '项目名称为必填项!',
              },
            ]}
          >
            <Input placeholder="请输入项目名称"></Input>
          </Form.Item>
          <Form.Item name='owner' label='负责人'>
            <Input placeholder="项目相负责人"></Input>
          </Form.Item>
          <Form.Item name="desc" label="更多信息">
            <TextArea/>
          </Form.Item>
        </Form>
      </Modal>

几个关键的代码增加后,运行看下效果,并进行测试,编辑的对话框表单的弹出和显示,数据初始化都正常,但是反填的数据只是第一次选中行的数据,即时已经设置了 destroyOnClose关闭对话框销毁子元素也无效。

经查找官方组件API下边有这样的说明:

你不能用控件的 value 或 defaultValue 等属性来设置表单域的值,默认值可以用 Form 里的 initialValues 来设置。注意 initialValues 不能被 setState 动态更新,你需要用 setFieldsValue 来更新。

也就是要想动态的替换是需要使用 setFieldsValue来实现,在此交互场景下initialValues/initialValue 更适合新增时候一些默认数据绑定。改动给出diff代码块如下。

const [formEditProject] = Form.useForm();
- //const [projectInfo, setProjectInfo] = useState({});

const editAction = (record) => {
- // setProjectInfo(record);
+ formEditProject.setFieldsValue(record);
  setProjectEditVisible(true);
}

<Form
+ form={formEditProject}
- // initialValues={projectInfo}
>

具体改动点涉及:

  • 定义useForm Hook 并绑定编辑Form;
  • 点击编辑按钮无需再赋予projectInfo,直接调用表单setFieldsValue动态赋值。

由于在之实现后端项目保存的时候其增加/修改都是一个接口,唯一的增量是编辑带原始ID,所以保存接口代码参考增加的onOK,请求参数里增加个ID字段,其他对应改成编辑modal绑定的变量。

onOk={() => {
  formEditProject
    .validateFields()
    .then(async (values) => {
    const data = {
      id: values.id,
      name: values.name,
      owner: values.owner,
      desc: values.desc,
      type: 'public',
      operator: '大奇'
    }
    const resp = await saveProduct(data);
    if (resp.success) {
      formEditProject.resetFields(); 
      setProjectEditVisible(false);
      reloadProjectList();
    }
  })
    .catch((info) => {
    console.log('修改项目信息失败', info);
  });
}}

五处改动点分别为

  • formEditProject
  • id: values.id
  • formEditProject.resetFields();
  • setProjectEditVisible(false);
  • console.log('修改项目信息失败', info);

带上这部分代码,重新编译做个测试,看下效果。


至此项目管理的增加和修改的数据交互操作,通过最基本Modal+Form组件已经实现了。类似这类字段较少的数据保存交互都可以通过这种方式实现了。

2.编写Component

随着应用的发展,会需要在多个页面分享 UI 元素 (或在一个页面使用多次),或是在一个tsx逻辑代码太多,这种情况下就可以把这部分抽成 component 。
我们来编写一个 UpsertProject component,再将增加和修改合并,这样就将大幅优化代码。
按照之前Template里的例子,新建 src/pages/Project/components/UpsertProject.tsx 文件:

import React, { useState } from 'react';

/* 抽离出来的组件,用于优化页面代码 */
const UpsertProject = (props) => {
  return (
    <>
    </>
  )
}

export default UpsertProject;

2.1 编码index.jsx

为了保留历史参考代码,增改组件的提取将全部定义新变量,源代码中将注释掉,如果是本地测试开发的话完全可以直接删掉。这里再想一个问题,在之前实现的新增和修改Modal+Form其实将近90%代码重复,所以我们可以将其合并,不同的部分通过条件判断进行逻辑操作和动态赋值。因此我们在 src/pages/Project/index.jsx 有如下新定义:

  // Components 需要留在上级的变量(重新定义原有的将注释掉)
  const [upsertVisible, setUpsertVisible] = useState(false); //控制抽离的组件显隐
  const [upsertAction, setUpsertAction] = useState('ADD'); // 标记组件的动作默认增加
  const [upsertDetail, setUpsertDetail] = useState({}); // 编辑动作下选择行详细信息

对应的也需要修改组件引用、涉及到增加和修改方法逻辑:

  • 原有p_add和p_edit可以全部删掉,使用新的组件
  • 新的组件里会用一些变量和方法直接透传
  • 修改addAction添加按钮方法逻辑,标记动作为ADD并设置状态显示
  • 修改editAction表操作列,标记动作为EDIT,赋值编辑行详细信息,同样设置状态显示
import UpsertProject from "@/pages/Project/components/UpsertProject";

const addAction = () => {
    // setProjectVisible(true);
    setUpsertAction("ADD");
    setUpsertVisible(true);
  }

const editAction = (record) => {
    // setProjectInfo(record);
    // formEditProject.setFieldsValue(record);
    // setProjectEditVisible(true);
    setUpsertDetail(record);
    setUpsertAction('EDIT');
    setUpsertVisible(true);
  }

return (
    <>
      <Button
       ...省略...
      >
        项目添加
      </Button>
  
      {/*原来的增加和修改Modal都可以删掉了, 引入抽离的组件*/}

      <UpsertProject
        upsertAction={upsertAction}
        upsertVisible={upsertVisible}
        setUpsertVisible={setUpsertVisible}
        upsertDetail={upsertDetail}
        reloadProjectList={reloadProjectList}
      />
     
      <Table
       ...省略...
      />
    </>
)

如果对比原index.jsx文件,代码量的减少直观让可阅读和逻辑性更好。

2.2 组件UpsertProject

接下来就要真正实现 UpsertProject 组件 内部逻辑了,基本上将p_edit的代码拷贝过来,然后额外进行页面渲染初始化逻辑判断,以及动态判断动作赋值和组件的显示隐藏,详细的解释请参考如下完整代码。其中有两个新知识点说明移步到下文useEffect和hidden学习。

import React, { useEffect, useState } from "react";
import { Form, Input, Modal } from "antd";
const { TextArea } = Input;

import { saveProduct } from "@/pages/Project/service";

/* 抽离出来的组件,用于优化页面代码 */
const UpsertProject = (props) => {
  const [form] = Form.useForm();

  // 副作用钩子,给定一个参数,当props内容有变化是执行此Hook
  useEffect(()=>{
    if(props.upsertAction==='EDIT'){
      form.setFieldsValue(props.upsertDetail);
    } else {
      form.resetFields();
    }
  },[props]) //

  return (
      <Modal
        title={props.upsertAction==='ADD'?'增加项目':'修改项目'} // 动态判断标题
        visible={props.upsertVisible}
        destroyOnClose
        onCancel={()=>props.setUpsertVisible(false)}
        onOk={() => {
          form
            .validateFields()
            .then(async (values) => {
              const data = {
                id: props.upsertAction==='ADD'? undefined: values.id, // 根据增加还是修改给定id值
                name: values.name,
                owner: values.owner,
                desc: values.desc,
                type: 'public',
                operator: '大奇'
              }
              const resp = await saveProduct(data);
              if (resp.success) {
                form.resetFields(); // 表单清除历史
                props.setUpsertVisible(false);
                props.reloadProjectList();
              }
            })
            .catch((info) => {
              console.log('保存项目信息失败', info);
            });
        }}
      >
        <Form form={form}>
          {/*通过hidden属性决定是否隐藏此项目,在新增操作时候隐藏*/}
          <Form.Item hidden={props.upsertAction==='ADD'} name='id' label='编号'>
            <Input disabled></Input>
          </Form.Item>
          <Form.Item
            name='name'
            label='名称'
            rules={[
              {
                required: true,
                message: '项目名称为必填项!',
              },
            ]}
          >
            <Input placeholder="请输入项目名称"></Input>
          </Form.Item>
          <Form.Item name='owner' label='负责人'>
            <Input placeholder="项目相负责人"></Input>
          </Form.Item>
          <Form.Item name="desc" label="更多信息">
            <TextArea/>
          </Form.Item>
        </Form>
      </Modal>
  )
}

export default UpsertProject;

2.3 集成测试

完成所有代码编写后,做个集成集成测试,强调一下笔者在边开发边总结文档的过程是一点点测试过来,非一次编码后才进行测试的,这里受于文章的排版才这样做的。

测试1:对话框显隐和赋值

  • Case1: 验证对话框弹出和关闭正常
  • Case2: 项目添加表单为空
  • Case3: 项目编辑值正确初始化

测试2:添加和修改保存操作

  • Case1: 验证项目新增保存成功
  • Case2: 验证项目修改保存成功
  • Case3: 保存成功后关闭对话框不刷新项目Table


3. useEffect和hidden

对于上自定义组件代码涉及到几个新出现的知识点,分别简单讲解下。

3.1钩子useEffect

为了实现项目增改组件数据的初始化,引入useEffect钩子, 可看作是React中 componentDidMountcomponentDidUpdatecomponentWillUnmount 几个声明周期的组合,这里主要的目的当透传的值有变化的时候,触发这个周期Hook,实现根据upsertAction判断是新增还是编辑操作,如果编辑使其表单动态初始化值,否则保持表单为空。

https://zh-hans.reactjs.org/docs/hooks-reference.html#useeffect

useEffect传递两个参数,第一个参数是逻辑处理函数,第二个参数是一个数组,其中

  • 如果参数二存放变量,当数组存放变量发生改变时,第一个参数,逻辑处理函数将会被执行;
  • 如果参数二不传,浏览器会无线循环执行逻辑处理函数;
  • 如果参数二传空数组[],相当于挂在完成后执行一次。

3.2组件隐藏Hidden

在React没有像Vue那种v-if的语法糖,在处理组件根据条件是否渲染的时候,比较正规的处理方式是写个函数定义组件,例如如下用法:显示那种按钮和文案根据给定值动态返回。

const showButton= (state) => {
  if (state==='OPEN'){
    return <Button type='link'>CLOSE</Button>
  } else {
    return <Button>OPEN</Button>
  }
}

<div>{showButton("OPEN")}</div>

但对于本篇中的仅是根据某种条件隐藏编号表单项即可,经验证有一种简单的用法,即大部分官方组件有Hidden的属性,所以我们值需要为这个隐藏字段赋予逻辑值true或false就能控制是否显示。比如代码中根据操作动作来确定此项目是否显示。

 <Form.Item hidden={props.upsertAction==='ADD'} name='id' label='编号'>
    <Input disabled></Input>
</Form.Item>

至此通过详细的讲解和演示实践,完成了项目管理的增加和编辑需求开发,学习开发过程成中,是否遇到什么问题呢?如果需要帮助和交流的可以加我,我尽量在时间允许的情况下回复你,一同探讨学习、动手实战,一起踏实成长。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mega Qi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值