使用 apollo hooks 和太多 re renders 的一般建议

General Advice with apollo hooks and too many re renders

背景:我正在尝试使用 DropZone 实现文件上传到 s3 和 graphql 服务预签名 url 用于放置和获取,虽然它可能不完美但确实有效。我现在遇到的问题是,当我添加 useMutation 以将结果推送到写入 mongodb 数据库的 graphlql 端时,我得到了太多的重新渲染,因此正在寻找有关如何真正理解这里发生的事情的建议。一旦我没有 addFileS3(file) addFileS3(file) addFileS3(file) 调用 useMutation 到 grpahql 将结果写入 mongoDB 所以我可以稍后检索文件点所以我假设最好 它的位置是 axios 的响应。

const DropZone = ({ folderId, folderProps }) => {
  const [createS3File] = useMutation(ADD_FILE_S3);
  const addFileS3 = (file) => {
    createS3File({
      variables: {
        folderId: folderId,
        fileName: file.name,
      },
    })
      .then(({ data }) => {
        console.log("data", data);
      })
      .catch((e) => {
        console.log(e);
      });
  };
  const {
    acceptedFiles,
    getRootProps,
    getInputProps,
    isDragActive,
    isDragAccept,
    isDragReject,
  } = useDropzone({ accept: "image/*, application/pdf" });
  const [
    getPutURL,
    { loading: loading_url, error: error_url, data: data_url },
  ] = useLazyQuery(GET_S3_PUT_URL);
  if (loading_url) {
    console.log("loading");
  } else if (error_url) {
    console.log(error_url);
  } else if (data_url) {
    const results = data_url.PUTURL;
    results.map((file) => {
      const fileResult = acceptedFiles.filter(function(fileAcc) {
        return fileAcc.name === file.name;
      });

      const options = {
        params: {
          Key: file.name,
          ContentType: file.type,
        },
        headers: {
          "Content-Type": file.type,
        },
      };
      axios
        .put(file.url, fileResult[0], options)
        .then((res) => {
          //once i add the below here or outside axios post it goes mental on uploads
          addFileS3(file);
        })
        .catch((err) => {
        });
    });
  }

  const acceptedFilesItems = acceptedFiles.map((file) => {
    return (
      <li key={file.path}>
        {file.path} - {file.size} bytes
      </li>
    );
  });

  const uploadDocs = () => {
    let files = [];
    acceptedFiles.map((file) => {
      const fileObj = { name: file.name, type: file.type };
      files.push(fileObj);
    });

    return getS3URLResult(files);
  };

  const getS3URLResult = async (files) => {
    getPutURL({
      variables: {
        packet: files,
      },
    });
  };



  return (
    <StyledDropZone>
      <div className="container">
        <Container
          {...getRootProps({ isDragActive, isDragAccept, isDragReject })}
        >
          <input {...getInputProps()} />
          <p>Drag 'n' drop some files here, or click to select files</p>
        </Container>
        {acceptedFilesItems}
      </div>
      <button onClick={() => uploadDocs(acceptedFiles)}>Upload</button>
    </StyledDropZone>
  );
};

您在渲染 'flow' 期间发出 axios 请求,而不是在事件 handler/chain 中。它被调用,改变状态并导致下一次重新渲染 - 无限循环。

突变和惰性查询都可以使用 onCompleted 处理程序。这是 chain/invoke 下一个操作的位置(使用 data 结果参数)。

... 否则 hanlder 不应该 return 任何东西 (return getS3URLResult(files);) - 只需调用它 (getS3URLResult(files);) 或直接 getPutURL.

更新

可能您正在寻找这样的东西:

const DropZone = ({ folderId, folderProps }) => {
  const {
    acceptedFiles,
    getRootProps,
    getInputProps,
    isDragActive,
    isDragAccept,
    isDragReject,
  } = useDropzone({ accept: "image/*, application/pdf" });

  const uploadDocs = () => {
    let files = [];
    acceptedFiles.map((file) => {
      const fileObj = { name: file.name, type: file.type };
      files.push(fileObj);
    });
    console.log("uploadDocs, from acceptedFiles", files);
    // return getS3URLResult(files);
    getPutURL({
      variables: {
        packet: files,
      },
    });

  };

  const [
    getPutURL,
    { loading: loading_url, error: error_url, data: data_url },
  ] = useLazyQuery(GET_S3_PUT_URL, {
    onCompleted: (data) => {
      console.log("PUT_URL", data);

      const results = data.PUTURL;
      results.map((file) => {
        const fileResult = acceptedFiles.filter(function(fileAcc) {
          return fileAcc.name === file.name;
        });
        const options = {
          params: {
            Key: file.name,
            ContentType: file.type,
          },
          headers: {
            "Content-Type": file.type,
          },
        };
        axios
          .put(file.url, fileResult[0], options)
          .then((res) => {
            console.log("axios PUT", file.url);
            // addFileS3(file);
            createS3File({
              variables: {
                folderId: folderId,
                fileName: file.name,
              },
            })
          })
          .catch((err) => {
          });
      });

    }
  });

  const [createS3File] = useMutation(ADD_FILE_S3,{
    onCompleted: (data) => {
      console.log("ADD_FILE_S3", data);
      //setUploadedFiles( uploadedFiles,concat(data.somefilename) );
    }
  });                         

  const [uploadedFiles, setUploadedFiles] = useState( [] );


  const acceptedFilesItems = acceptedFiles.map((file) => {
    return (
      <li key={file.path}>
        {file.path} - {file.size} bytes
      </li>
    );
  });   

  const renderUploadedFiles ...

  return (
    <StyledDropZone>
      <div className="container">
        <Container
          {...getRootProps({ isDragActive, isDragAccept, isDragReject })}
        >
          <input {...getInputProps()} />
          <p>Drag 'n' drop some files here, or click to select files</p>
        </Container>
        {acceptedFilesItems}
        {uploadedFiles.length && <div class="success">
          {renderUploadedFiles}
        </div>}
      </div>
      <button onClick={() => uploadDocs(acceptedFiles)}>Upload</button>
    </StyledDropZone>
  );
};

应该添加一些优化(useCallback),为了清楚起见没有放置。

为了提高可读性和优化(限制重新渲染)...我会将几乎所有(处理)移动到单独的子组件中 - 将 acceptedFiles 作为 prop 传递,在内部渲染上传按钮。