【问题标题】:Is refactoring with a separate function possible?是否可以使用单独的函数进行重构?
【发布时间】:2023-03-08 05:58:01
【问题描述】:

我有一个表格组件,其中许多表格列的处理方式类似。 有没有办法优化这段代码,也许在一个单独的函数中?

import useTableStyles from 'admin/components/table/AdminTable.styles';
import useStyles from 'portal/pages/wasOperators/views/ViewEditOperators.style';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { toast } from 'react-toastify';
import { Form } from 'shared/components/Form';
import WssoService from 'shared/services/WssoService';

import DateFnsUtils from '@date-io/date-fns';
import { Button, Grid, TextField } from '@material-ui/core';
import { DatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers';

interface Props {
  isEditing?: boolean;
}

const BenchmarkingTable: React.FC<Props> = ({ isEditing }) => {
  const tableClasses = useTableStyles();
  const classes = useStyles();
  const { t } = useTranslation();
  const { operatorsId }: any = useParams();

  const valuesForInverseCalculation = [
    'continuityWaterSupply',
    'totalLossesWaterSupplySystems',
    'pressureWaterSupplySystem',
    'sewerNetworkAccidents',
    'floodsThirdPartyCausedBySewage',
  ];

  const benchmarkingDetailsInit = {
    levelWaterSupplyServices: {
      target: '',
      result: '',
      status: '',
    },
    qualityDrinkWaterLargeAreas: {
      target: '',
      result: '',
      status: '',
    },
    qualityDrinkWaterSmallAreas: {
      target: '',
      result: '',
      status: '',
    },
    monitorQualityDrinkWater: {
      target: '',
      result: '',
      status: '',
    },
    continuityWaterSupply: {
      target: '',
      result: '',
      status: '',
    },
    totalLossesWaterSupplySystems: {
      target: '',
      result: '',
      status: '',
    },
    pressureWaterSupplySystem: {
      target: '',
      result: '',
      status: '',
    },
    levelCoverageServiceDisposalOfWastewater: {
      target: '',
      result: '',
      status: '',
    },
    levelCoverageServiceTreatmentOfWastewater: {
      target: '',
      result: '',
      status: '',
    },
    wastewaterQuality: {
      target: '',
      result: '',
      status: '',
    },
    sewerNetworkAccidents: {
      target: '',
      result: '',
      status: '',
    },
    floodsThirdPartyCausedBySewage: {
      target: '',
      result: '',
      status: '',
    },
  };
  const [data, setData] = useState<any>(benchmarkingDetailsInit);
  const [KEY, setKEY] = useState<any>(new Date());

  useEffect(() => {
    (async () => {
      const result: any = await WssoService.getBenchmarking(
        operatorsId,
        KEY.getFullYear()
      );
      if (result && result.data && result.data.json) {
        setData(JSON.parse(result.data.json));
      } else {
        setData(null);
      }
    })();
    /* eslint-disable react-hooks/exhaustive-deps */
  }, [KEY, operatorsId]);

  const HandleKEYChange = () => {
    return (
      <MuiPickersUtilsProvider utils={DateFnsUtils}>
        <DatePicker
          variant="inline"
          inputVariant="outlined"
          format={'yyyy'}
          views={['year']}
          onChange={setKEY}
          value={KEY}
        />
      </MuiPickersUtilsProvider>
    );
  };

  const updateBenchmarking = async (editValues: any) => {
    const request = {
      json: JSON.stringify(editValues),
    };
    const result: any = await WssoService.editAddBenchmarking(
      operatorsId,
      KEY.getFullYear(),
      request
    );
    if (result && result.data && result.json) {
      setData(JSON.parse(result.data.json));
    }
  };

  const onUpdateSuccess = () => {
    toast.success(t('itemUpdateSuccessfully'));
  };

  const handleSaveData = async () => {
    if (data) {
      await updateBenchmarking(data);
      onUpdateSuccess();
    }
  };

  const handleOnTextChange = (
    key: string,
    valueKey: string,
    e: React.ChangeEvent<HTMLInputElement>
  ) => {
    const value = e.target.value;
    const newData = Object.assign({}, data, {
      [key]: Object.assign({}, data[key], {
        [valueKey]: value,
      }),
    });
    setData(newData);
  };

  const handleColorChange = (name: any, target: any, result: any) => {
    const removePercentFromTarget = target.includes('%')
      ? target.slice(0, -1)
      : target;

    const convertedTarget = Number(removePercentFromTarget);

    const removePercentFromResult = result.includes('%')
      ? result.slice(0, -1)
      : result;

    const convertedResult = Number(removePercentFromResult);

    target = convertedTarget;
    result = convertedResult;

    let final: any;

    const arrOfNames = [];
    arrOfNames.push(name);

    if (arrOfNames.some(x => valuesForInverseCalculation.includes(x))) {
      if (target < result) {
        Object.keys(data).map(k => {
          return (final = data[k].status = 'red');
        });
      } else if (target > result) {
        Object.keys(data).map(k => {
          return (final = data[k].status = 'green');
        });
      } else if (target === result) {
        Object.keys(data).map(k => {
          return (final = data[k].status = 'yellow');
        });
      }
    } else {
      if (target > result) {
        Object.keys(data).map(k => {
          return (final = data[k].status = 'red');
        });
      } else if (target < result || (target === result && result === 100)) {
        Object.keys(data).map(k => {
          return (final = data[k].status = 'green');
        });
      } else if (target === result) {
        Object.keys(data).map(k => {
          return (final = data[k].status = 'yellow');
        });
      }
    }

    return final;
  };

  const updateData = (newData: any[]) => {
    setData(newData);
  };

  const renderBenchmarkDetails = () => {
    if (isEditing) {
      return data ? (
        Object.keys(data).map(k => {
          return (
            <tr>
              <td>{t(k)}</td>
              <td className={classes.benchTextfieldAlign}>
                <TextField
                  type="text"
                  onChange={(e: any) => handleOnTextChange(k, 'target', e)}
                  value={data[k].target}
                  inputProps={{ min: 0, style: { textAlign: 'center' } }}
                  required
                />
              </td>
              <td className={classes.benchTextfieldAlign}>
                <TextField
                  type="text"
                  onChange={(e: any) => handleOnTextChange(k, 'result', e)}
                  value={data[k].result}
                  inputProps={{ min: 0, style: { textAlign: 'center' } }}
                  required
                />
              </td>
            </tr>
          );
        })
      ) : (
        <tr>
          <td>{t('noDetailsToDisplay')}</td>
          <td>{t('noDetailsToDisplay')}</td>
          <td>{t('noDetailsToDisplay')}</td>
          <td>{t('noDetailsToDisplay')}</td>
        </tr>
      );
    } else {
      return data ? (
        Object.keys(data).map((rowName, i) => {
          return (
            <tr key={i}>
              <td>{t(rowName)}</td>
              <td className={classes.benchTextfieldAlign}>
                {data[rowName].target}
              </td>
              <td className={classes.benchTextfieldAlign}>
                {data[rowName].result}
              </td>
              <td className={classes.benchTextfieldAlign}>
                <div
                  style={{
                    width: '15px',
                    height: '15px',
                    borderRadius: '50%',
                    backgroundColor: handleColorChange(
                      rowName,
                      data[rowName].target,
                      data[rowName].result
                    ),
                  }}
                />
              </td>
            </tr>
          );
        })
      ) : (
        <tr>
          <td>{t('noDetailsToDisplay')}</td>
          <td>{t('noDetailsToDisplay')}</td>
          <td>{t('noDetailsToDisplay')}</td>
          <td>{t('noDetailsToDisplay')}</td>
        </tr>
      );
    }
  };

  const handleExport = useCallback(async () => {
    const dataRows: any = [];

    if (data) {
      Object.keys(data).map(rowName => {
        dataRows.push([t(rowName), data[rowName].target, data[rowName].result]);
        return null;
      });
    }

    let dataToCSV: any[][] = [];

    if (dataRows && dataRows.length > 0) {
      dataToCSV = [[t('criteria'), t('targets'), t('results')], ...dataRows];

      const csvContent: string =
        'data:text/csv;charset=utf-8,\uFEFF' +
        dataToCSV
          .map(e =>
            e
              .map(r =>
                r instanceof Array
                  ? `"${(r ?? '').toString().replace(',', ', ')}"`
                  : (r ?? '').toString().replace(',', ' ')
              )
              .join(',')
          )
          .join('\n');

      const encodedUri: string = encodeURI(csvContent);

      const link: HTMLAnchorElement = document.createElement('a');
      link.setAttribute('href', encodedUri);
      link.setAttribute('download', `${t('exportedTableData')}.csv`);

      link.click();
    }
  }, [data, t]);
console.log(operatorsId);
console.log(benchmarkingDetailsInit);
console.log(updateBenchmarking);
  return (
    <Grid>
      <Form
        onSubmit={(formData, { resetForm }) => {
          const tempData = !!operatorsId
            ? {
                ...formData,
              }
            : {
                ...formData,
                ...(!!operatorsId && {
                  operatorsAreaEntity: {
                    id: parseInt(operatorsId, 0),
                  },
                }),
                id: operatorsId ? undefined : Date.now(),
              };
          updateData([tempData, ...data]);
          resetForm();
        }}
        initialValues={benchmarkingDetailsInit}
        enableReinitialize={true}
      >
        <div className={classes.tableContainer}>
          <Grid item xs={12}>
            <div className={classes.containerLegendBench}>
              <div
                style={{
                  display: 'flex',
                  justifyContent: 'flex-end',
                }}
              >
                <div className={classes.yearBoxBenchmarketType}>
                  <HandleKEYChange />
                </div>
                <div className={classes.legendBoxBenchmarketType}>
                  {t('reachedResult')}
                  <div
                    style={{
                      marginLeft: '3px',
                      width: '11px',
                      height: '9px',
                      borderRadius: '50%',
                      backgroundColor: '#006400',
                      display: 'inline-block',
                    }}
                  />
                </div>
                <div className={classes.legendBoxBenchmarketType}>
                  {t('almostReachedResult')}
                  <div
                    style={{
                      marginLeft: '3px',
                      width: '11px',
                      height: '9px',
                      borderRadius: '50%',
                      backgroundColor: '#FFDF00',
                      display: 'inline-block',
                    }}
                  />
                </div>
                <div className={classes.legendBoxBenchmarketType}>
                  {t('notReachedResult')}
                  <tr
                    style={{
                      marginLeft: '3px',
                      width: '11px',
                      height: '9px',
                      borderRadius: '50%',
                      backgroundColor: '#FF0000',
                      display: 'inline-block',
                    }}
                  />
                </div>
              </div>
            </div>
          </Grid>
          <table className={tableClasses.table}>
            <thead>
              <tr>
                <th>{t('criteria')}</th>
                <th>{t('targets')}</th>
                <th>{t('results')}</th>
                <th />
              </tr>
            </thead>
            <tbody>{renderBenchmarkDetails()}</tbody>
          </table>
        </div>
        <Grid item xs={12}>
          <Button
            style={{ margin: '5px 0px 10px 10px' }}
            type="button"
            color="primary"
            variant="contained"
            onClick={handleExport}
            disabled={!data}
          >
            {t('export')}
          </Button>
        </Grid>
        {isEditing && (
          <Button onClick={handleSaveData} className={classes.submitButton}>
            {t('save')} <i className={`fas fa-save ${classes.submitIcon}`} />
          </Button>
        )}
      </Form>
    </Grid>
  );
};

export default BenchmarkingTable;

【问题讨论】:

  • 你指的是什么代码?我只能看到两个图像。请您将代码作为文本添加到问题中作为minimal reproducible example
  • 至少提供图片的文字版
  • 仅来自该文件的整个代码 - 来自代码框,抱歉,我是新来的 :)
  • 欢迎来到 Stack Overflow!以下是如何获得更多帮助。 1. 编辑您的问题并将相关代码粘贴为代码块中的文本。然后,我们可以在创建您的答案时复制并粘贴该代码。 2. 多描述一下你的问题。目前还不清楚“用单独的函数重构是什么意思”。

标签: javascript reactjs typescript


【解决方案1】:

你有 60 行代码来初始化一个空对象!似乎该对象的每个键都是表的一行,并且每个值都具有相同的格式:

interface Benchmark {
  target: string;
  result: string;
  status: string;
}

所以data 可以描述为Record&lt;string, Benchmark&gt;。尽管将name 设为属性并让data 设为array 可能更有意义。

&lt;td&gt;{t('noDetailsToDisplay')}&lt;/td&gt; 的条件永远不会受到影响,因为您已将 data 初始化为空对象,因此 data 始终为 true

如果您想在没有数据的情况下显示不同的内容,那么您应该执行以下任一操作:

  1. 使用undefined 初始状态。
  2. 拥有一个单独的 isLoading 状态,您可以使用 useEffect 挂钩设置。
  3. 检查Object.keys(data).length &gt; 0 而不是检查!! data

您的代码中看起来非常不合逻辑的另一部分是handleColorChange 函数。尽管名称具有误导性,但似乎此函数旨在根据 resulttarget 值计算颜色字符串。这个颜色是status 属性,你可以改变状态来获得它。相反,您应该在获取数据之后并将其设置为状态之前计算颜色并将其添加为 status


如果你想要一行有 4 个空单元格,你可以这样做:

<tr>
  {Array.from({ length: 4 }).map((_, i) => (
    <td key={i}>{t("noDetailsToDisplay")}</td>
  ))}
</tr>

你在两个地方有相同的 JSX 块,所以你想修复它。


我还没有解决所有问题,但我已经解决了很多问题。

import useTableStyles from 'admin/components/table/AdminTable.styles';
import useStyles from 'portal/pages/wasOperators/views/ViewEditOperators.style';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { toast } from 'react-toastify';
import { Form } from 'shared/components/Form';
import WssoService from 'shared/services/WssoService';
import DateFnsUtils from '@date-io/date-fns';
import { Button, Grid, TextField } from '@material-ui/core';
import { DatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers';


interface BenchmarkFromService {
  target: string;
  result: string;
}

interface BenchmarkRow extends BenchmarkFromService {
  name: string;
  status: string;
}

const BenchmarkingTable: React.FC<Props> = ({ isEditing }) => {
  const tableClasses = useTableStyles();
  const classes = useStyles();
  const { t } = useTranslation();
  const { operatorsId } = useParams<{ operatorsId: string }>();

  /**
   * each row of the table is a Benchmark
   */
  const [data, setData] = useState<BenchmarkRow[]>([]);
  const [KEY, setKEY] = useState(new Date());

  // extract shared logic from "get" and "update" responses
  // memoized so it's safe as a useEffect dependency
  const handleResult = useCallback(
    // is this really any?  what is it when it's not { data: { json: string } }
    (result: any) => {
      /**
       * regular colors and inverted colors have the same logic, just swap the target and result
       */
      const computeColor = (a: number, b: number): string => {
        if (a > b) {
          return "red";
        } else if (a < b || (a === b && b === 100)) {
          return "green";
        } else if (a === b) {
          return "yellow";
        }
      };

      const valuesForInverseCalculation = [
        "continuityWaterSupply",
        "totalLossesWaterSupplySystems",
        "pressureWaterSupplySystem",
        "sewerNetworkAccidents",
        "floodsThirdPartyCausedBySewage"
      ];

      const toNum = (value: string): number =>
        parseFloat(value.replace("%", ""));

      try {
        const raw: Record<string, BenchmarkFromService> = JSON.parse(
          result.data.json
        );
        const formatted = Object.entries(raw).map(
          ([name, { target, result }]) => ({
            name,
            target,
            result,
            status: valuesForInverseCalculation.includes(name)
              ? computeColor(toNum(result), toNum(target))
              : computeColor(toNum(target), toNum(result))
          })
        );
        setData(formatted);
      } catch (e) {
        // catch errors from malformatted response or unparsable JSON
        setData([]);
      }
    },
    []
  );

  useEffect(() => {
    (async () => {
      const result: any = await WssoService.getBenchmarking(
        operatorsId,
        KEY.getFullYear()
      );
      handleResult(result);
    })();
  }, [KEY, operatorsId, handleResult]);

  const updateBenchmarking = async (editValues: any) => {
    const request = {
      json: JSON.stringify(editValues)
    };
    const result: any = await WssoService.editAddBenchmarking(
      operatorsId,
      KEY.getFullYear(),
      request
    );
    handleResult(result);
  };

  const onUpdateSuccess = () => {
    toast.success(t("itemUpdateSuccessfully"));
  };

  const handleSaveData = async () => {
    if (data) {
      await updateBenchmarking(data);
      onUpdateSuccess();
    }
  };

  const renderCell = (property: keyof BenchmarkRow, row: BenchmarkRow) => {
    const onChange = (
      e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
    ) => {
      setData((previous) =>
        previous.map((obj) =>
          obj.name === row.name
            ? {
                ...obj,
                [property]: e.target.value
              }
            : obj
        )
      );
    };

    return (
      <td className={classes.benchTextfieldAlign}>
        {isEditing ? (
          <TextField
            type="text"
            onChange={onChange}
            value={row[property]}
            inputProps={{ min: 0, style: { textAlign: "center" } }}
            required
          />
        ) : (
          row[property]
        )}
      </td>
    );
  };

  const renderBenchmarkDetails = () => {
    if (data.length === 0) {
      return (
        <tr>
          {Array.from({ length: 4 }).map((_, i) => (
            <td key={i}>{t("noDetailsToDisplay")}</td>
          ))}
        </tr>
      );
    }
    return data.map((row) => (
      <tr key={row.name}>
        <th scope="row">{row.name}</th>
        {renderCell("target", row)}
        {renderCell("result", row)}
        <td className={classes.benchTextfieldAlign}>
          <div
            style={{
              width: "15px",
              height: "15px",
              borderRadius: "50%",
              backgroundColor: row.status
            }}
          />
        </td>
      </tr>
    ));
  };

  const handleExport = useCallback(async () => {
    if (data.length === 0) {
      return;
    }
    const dataRows = data.map((row) => [t(row.name), row.target, row.result]);
    const dataToCSV = [
      [t("criteria"), t("targets"), t("results")],
      ...dataRows
    ];

    const csvContent: string =
      "data:text/csv;charset=utf-8,\uFEFF" +
      dataToCSV
        .map((row) =>
          row
            .map(
              (cell) => cell.replace(",", "\\,") // I think it's ok to escape the comma like this?
            )
            .join(",")
        )
        .join("\n");

    const encodedUri: string = encodeURI(csvContent);

    const link: HTMLAnchorElement = document.createElement("a");
    link.setAttribute("href", encodedUri);
    link.setAttribute("download", `${t("exportedTableData")}.csv`);

    link.click();
  }, [data, t]);

  return (
    <Grid>
      <Form
        onSubmit={(formData, { resetForm }) => {
          const tempData = !!operatorsId
            ? {
                ...formData
              }
            : {
                ...formData,
                ...(!!operatorsId && {
                  operatorsAreaEntity: {
                    id: parseInt(operatorsId, 0)
                  }
                }),
                id: operatorsId ? undefined : Date.now()
              };
          setData([tempData, ...data]);
          resetForm();
        }}
        initialValues={{}}
        enableReinitialize={true}
      >
        <div className={classes.tableContainer}>
          <Grid item xs={12}>
            <div className={classes.containerLegendBench}>
              <div
                style={{
                  display: "flex",
                  justifyContent: "flex-end"
                }}
              >
                <div className={classes.yearBoxBenchmarketType}>
                  <MuiPickersUtilsProvider utils={DateFnsUtils}>
                    <DatePicker
                      variant="inline"
                      inputVariant="outlined"
                      format={"yyyy"}
                      views={["year"]}
                      onChange={setKEY}
                      value={KEY}
                    />
                  </MuiPickersUtilsProvider>
                </div>
                <div className={classes.legendBoxBenchmarketType}>
                  {t("reachedResult")}
                  <div
                    style={{
                      marginLeft: "3px",
                      width: "11px",
                      height: "9px",
                      borderRadius: "50%",
                      backgroundColor: "#006400",
                      display: "inline-block"
                    }}
                  />
                </div>
                <div className={classes.legendBoxBenchmarketType}>
                  {t("almostReachedResult")}
                  <div
                    style={{
                      marginLeft: "3px",
                      width: "11px",
                      height: "9px",
                      borderRadius: "50%",
                      backgroundColor: "#FFDF00",
                      display: "inline-block"
                    }}
                  />
                </div>
                <div className={classes.legendBoxBenchmarketType}>
                  {t("notReachedResult")}
                  <tr
                    style={{
                      marginLeft: "3px",
                      width: "11px",
                      height: "9px",
                      borderRadius: "50%",
                      backgroundColor: "#FF0000",
                      display: "inline-block"
                    }}
                  />
                </div>
              </div>
            </div>
          </Grid>
          <table className={tableClasses.table}>
            <thead>
              <tr>
                <th>{t("criteria")}</th>
                <th>{t("targets")}</th>
                <th>{t("results")}</th>
                <th />
              </tr>
            </thead>
            <tbody>{renderBenchmarkDetails()}</tbody>
          </table>
        </div>
        <Grid item xs={12}>
          <Button
            style={{ margin: "5px 0px 10px 10px" }}
            type="button"
            color="primary"
            variant="contained"
            onClick={handleExport}
            disabled={!data}
          >
            {t("export")}
          </Button>
        </Grid>
        {isEditing && (
          <Button onClick={handleSaveData} className={classes.submitButton}>
            {t("save")} <i className={`fas fa-save ${classes.submitIcon}`} />
          </Button>
        )}
      </Form>
    </Grid>
  );
};

export default BenchmarkingTable;

【讨论】:

  • 非常感谢,琳达,这真的很有帮助! :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-15
  • 1970-01-01
  • 2023-03-10
  • 2014-12-24
  • 2017-07-11
  • 2022-07-01
相关资源
最近更新 更多