当前位置: 首页 > 知识库问答 >
问题:

react基础问题,应该是基础问题?

曾新立
2025-07-03

目前有一段react代码表单,其中一个表单项“使用模型”是可以增加多个子选项,并且是下拉组件。问题是增加多个之后,比如5个,然后再第二个下拉组件进行选择之后,字选项总数量就变成了2个。

import React, { useState } from 'react';
import type { FC } from 'react';
import type { UploadFile, UploadProps } from 'antd/es/upload/interface';
import { message, Upload } from 'antd';
import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons';
import {
  ModalForm,
  ProForm,
  ProFormUploadDragger,
  ProFormSelect,
} from '@ant-design/pro-components';
import { uploadFileApi } from '../service';
import _ from 'lodash';

interface ModelItem {
  key: number;
  value: string;
}

interface AddTagParsingModalProps {
  visible: boolean;
  onCancel?: () => void;
  onOk?: (formData: FormData) => void;
  loading?: boolean;
}

const modelOptions = [
  { label: '模型A', value: 'A' },
  { label: '模型B', value: 'B' },
  // ... 其他选项
];

const AddTagParsingModal: FC<AddTagParsingModalProps> = ({
  visible,
  onCancel,
  onOk,
  loading = false,
}) => {
  const [fileList, setFileList] = useState<UploadFile[]>([]);
  const [fileUrl, setFileUrl] = useState<string>('');
  const [fileError, setFileError] = useState<string>('');
  const [models, setModels] = useState<ModelItem[]>([{ key: Date.now(), value: '' }]);

  // 文件校验
  const beforeUpload: UploadProps['beforeUpload'] = (file) => {
    const isExcel =
      file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
      file.type === 'application/vnd.ms-excel';
    if (!isExcel) {
      setFileError('只支持上传Excel文件');
      message.error('只支持上传Excel文件');
      return Upload.LIST_IGNORE;
    }
    setFileError('');
    return true;
  };

  // 文件上传变更
  const handleFileChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
    uploadFileApi(fileList[0].originFileObj as File).then((res: any) => {
      setFileList(newFileList);
      setFileUrl(res.data.url);
    }).catch((err: any) => {
      setFileError(err.message);
    });
  };

  // 增加模型
  const addModel = (idx: number) => {
    // if (models.length >= 5) return;
    // const newModel = { key: Date.now(), value: '' };
    // setModels([...models.slice(0, idx), newModel, ...models.slice(idx)]);
    // console.log(models)
    setModels([...models, { key: Date.now(), value: '' }]);
  };

  // 删除模型
  const removeModel = (key: number) => {
    if (models.length === 1) return;
    setModels(models.filter((item) => item.key !== key));
  };

  // 修复bug:模型输入变更时,正确更新对应项的value
  const handleModelChange = (idx: number, value: string) => {
    // const tempModel = _.cloneDeep(models);
    // tempModel[idx].value = value;
    setModels(models);
    // console.log(models)
  };

  // 提交
  const handleFinish = async () => {
    if (fileList.length === 0) {
      setFileError('请上传文件');
      message.error('请上传文件');
      return false;
    }
    if (fileError) {
      message.error(fileError);
      return false;
    }
    // 校验所有模型都已选择
    if (models.some(item => !item.value)) {
      message.error('请为每个模型选择一个选项');
      return false;
    }
    const formData = new FormData();
    formData.append('file', fileUrl);
    formData.append('models', JSON.stringify(models.map((item) => item.value)));
    if (onOk) {
      onOk(formData);
    }
    return true;
  };

  // 关闭
  const handleModalCancel = () => {
    setFileList([]);
    setFileError('');
    setModels([{ key: Date.now(), value: '' }]);
    if (onCancel) onCancel();
  };

  return (
    <ModalForm
      title="新建标签解析"
      visible={visible}
      width={500}
      modalProps={{
        confirmLoading: loading,
        destroyOnClose: true,
        maskClosable: false,
        okText: '确定',
        cancelText: '关闭',
      }}
      onFinish={handleFinish}
      onVisibleChange={(open) => {
        if (!open) handleModalCancel();
      }}
      submitter={{
        searchConfig: {
          submitText: '确定',
          resetText: '关闭',
        },
        resetButtonProps: {
          onClick: (e: any) => {
            e.preventDefault();
            handleModalCancel();
          },
        },
      }}
      preserve={false}
    >
      <ProFormUploadDragger
        name="dragger"
        label="上传文件"
        max={1}
        description="只能上传xlsx文件"
        fieldProps={{
          fileList,
          beforeUpload,
          onChange: handleFileChange,
          accept: '.xlsx',
        }}

        required
        rules={[
          {
            validator: async () => {
              if (fileList.length === 0) {
                throw new Error('请上传文件');
              }
              if (fileError) {
                throw new Error(fileError);
              }
            },
          },
        ]}
        extra={fileError}
      />
      {JSON.stringify(models)}
      <ProForm.Item
        label="使用模型"
        required
        style={{ marginBottom: 0 }}
      >
        {models.map((item, idx) => (
          <div key={item.key} style={{ display: 'flex', alignItems: 'center', marginBottom: 8 }}>
            <div style={{ flex: 1 }}>
              <ProFormSelect
                style={{ width: '300px' }}
                placeholder={`请选择模型${models.length > 1 ? `(${idx + 1})` : ''}`}
                fieldProps={{
                  value: item.value,
                  onChange: (value) => handleModelChange(idx, value),
                  style: { width: '100%' },
                  allowClear: true,
                }}
                options={modelOptions}
                // name属性不再影响models的数量
                name={undefined}
                rules={[{ required: true, message: '请选择模型' }]}
                noStyle
              />
            </div>
            <PlusCircleOutlined
              className="dynamic-add-button"
              onClick={() => addModel(idx + 1)}
              style={{
                marginLeft: 8,
                color: models.length >= 5 ? 'gray' : 'red',
                fontSize: 16,
                verticalAlign: 'middle',
                pointerEvents: models.length >= 5 ? 'none' : 'auto'
              }}
            />
            <MinusCircleOutlined
              className="dynamic-delete-button"
              style={{
                marginLeft: 8,
                color: models.length === 1 ? 'gray' : 'red',
                pointerEvents: models.length === 1 ? 'none' : 'auto',
                fontSize: 16,
                verticalAlign: 'middle',
              }}
              onClick={() => {
                removeModel(item.key);
              }}
            />
          </div>
        ))}
      </ProForm.Item>
    </ModalForm>
  );
};

export default AddTagParsingModal;

共有1个答案

小牛23518
2025-07-03

我看了下你的代码在 handleModelChange 函数中写的是:

const handleModelChange = (idx: number, value: string) => {
  setModels(models);
};

这行代码并没有更新 models 中对应项的 value,只是把原来的 models 重新设置了一遍,没有任何变化。这就导致 React 没有检测到状态变化,或者状态被错误地重置。

需要在 handleModelChange 中 正确地更新指定索引的模型项的值。可以这样写:

const handleModelChange = (idx: number, value: string) => {
  const updatedModels = [...models];
  updatedModels[idx].value = value;
  setModels(updatedModels);
};

或者使用 map 更函数式一点:

const handleModelChange = (idx: number, value: string) => {
  setModels(models.map((item, index) =>
    index === idx ? { ...item, value } : item
  ));
};
 类似资料:
  • 如何获取$HTTP_RAW_POST_DATA $content = $this->request()->getBody()->__toString(); $raw_array = json_decode($content, true); 如何获取客户端IP 举例,如何在控制器中获取客户端IP //真实地址 $ip = ServerManager::getInstance()->getServer

  • 垃圾回收机制。。。(主要从下面几方面解答 GC原理、最好画图解释一下年轻代(Eden区和Survival区)、年老代、比例分配及为啥要这样分代回收) 对象分配问题,堆栈里的问题,详细的会问道方法区、堆、程序计数器、本地方法栈、虚拟机栈,问题入口从String a,new String(“”)开始 关键字,private protected public static final 组合着问 Obje

  • 如出现以下报错,可根据本FAQ,进行问题自查 1. 问题表现 问题表现1:代码报错 result:0---error:Error Domain=FATDomain Code=0 "基础库下载失败" UserInfo={NSLocalizedDescription=基础库下载失败} 问题表现2:打开小程序报错 2. 问题排查 2.1 使用符合基础库版本的SDK版本 在 运行时-基础库版本对照表 页

  • CURL发送POST请求服务器端超时 CURL在发送较大的POST请求时会先发一个100-continue的请求,如果收到服务器的回应才会发送实际的POST数据。而swoole_http_server不支持100-continue,就会导致CURL请求超时。 解决办法是关闭CURL的100-continue // 创建一个新cURL资源 $ch = curl_init(); //

  • 一个测试 Object.assign() 的例子。执行顺序感觉应该是obj.status = item.status?'通过':'未通过' 在 Object.assign(obj,item) 前面才对,实际显然不对。为什么是先合并后改变状态?

  • 本章将指导你了解 React 的基础知识。由于静态组件会有些枯燥,所以这章的内容会包含组件的状态与交互。此外,你将学习使用不同方式声明组件以及如何保持组件的可组合性和可复用性。准备好创造你自己的组件。 组件内部状态 组件内部状态也被称为局部状态,允许你保存、修改和删除存储在组件内部的属性。使用 ES6 类组件可以在构造函数中初始化组件的状态。 构造函数只会在组件初始化时调用一次。 让我们引入类构造

  • 不要被各种关于 reducers, middleware, store 的演讲所蒙蔽 —— Redux 实际是非常简单的。如果你有 Flux 开发经验,用起来会非常习惯。没用过 Flux 也不怕,很容易! 下面的教程将会一步步教你开发简单的 Todo 应用。 Action Reducer Store 数据流 搭配 React 示例:Todo 列表

  • StackExchange.Redis 中核心对象是在 StackExchange.Redis 命名空间中的 ConnectionMultiplexer 类,这个对象隐藏了多个服务器的详细信息。 因为ConnectionMultiplexer要做很多事,它被设计为在调用者之间可以共享和重用。 你不应该在执行每一个操作的时候就创建一个 ConnectionMultiplexer. 它完全是线程安全的