应如何在 Ant Design Upload 组件中设置 customRequest 以使用 XMLHttpRequest?

IT技术 javascript reactjs xmlhttprequest antd
2021-05-14 21:52:56

我有一个完全混乱的组件。现在我传递了一个函数,我一直在尝试一百万件我无法让它工作的事情。

export default class DatafileUpload extends Component {
  initialState = {
    fileUploading: false,
    fileList: [],
    status: 'empty', // 'empty' | 'active' | 'success' | 'exception'
    file: {}
  }

  state = this.initialState

  static propTypes = {
    userId: PropTypes.string.isRequired,
    datasetId: PropTypes.string.isRequired
  }

  scrubFilename = (filename) => filename.replace(/[^\w\d_\-.]+/ig, '')

  requestSignedS3Url = (file) => {
    const filename = this.scrubFilename(file.name)
    const params = {
      userId: this.props.userId,
      contentType: file.type,
      Key: `${filename}`
    };
    return api.get('/s3/signUpload', { params })
      .then(response => {
        return response.data;
      })
      .catch(error => {
        console.error(error);
      });
  }

  uploadFile = (file) => {
    this.requestSignedS3Url(file)
      .then(signResult => this.uploadToS3(file, signResult))
      .catch(error => console.log(error))
  }

  createCORSRequest = (method, url, opts) => {
    opts = opts || {};
    let xhr = new XMLHttpRequest();
    if (xhr.withCredentials != null) {
      xhr.open(method, url, true);
      if (opts.withCredentials != null) {
        xhr.withCredentials = opts.withCredentials;
      }
    } else if (typeof XDomainRequest !== "undefined") {
      xhr = new XDomainRequest();
      xhr.open(method, url);
    } else {
      xhr = null;
    }
    return xhr;
  };

  stepFunctions = () => {
    return {
      preprocess: (file) => {
        console.log('Pre-process: ' + file.name);
      },
      onProgress: (percent, message, file) => {
        this.setState({ fileUploading: true })
        console.log('Upload progress: ' + percent + '% ' + message);
      },
      onFinish: (signResult) => {
        this.setState({ fileUploading: false })
        console.log("Upload finished: " + signResult.publicUrl)
      },
      onError: (message) => {
        this.setState({ fileUploading: false })
        console.log("Upload error: " + message);
      },
      scrubFilename: (filename) => {
        return filename.replace(/[^\w\d_\-\.]+/ig, '');
      },
      onFinishS3Put: (signResult, file) => {
        console.log(signResult)
        return console.log('base.onFinishS3Put()', signResult.publicUrl);
      }
    }
  }

  uploadToS3 = async (file, signResult) => {
    const xhr = await this.createCORSRequest('PUT', signResult.signedUrl);
    const functions = this.stepFunctions()
    functions.preprocess(file)
    if (!xhr) {
      functions.onError('CORS not supported', file);
    } else {
      xhr.onload = () => {
        if (xhr.status === 200) {
          functions.onProgress(100, 'Upload completed', file);
          return functions.onFinishS3Put('potatopotato', file);
        } else {
          return functions.onError('Upload error: ' + xhr.status, file);
        }
      };
      xhr.onerror = () => {
        return functions.onError('XHR error', file);
      };
      xhr.upload.onprogress = (e) => {
        let percentLoaded;
        if (e.lengthComputable) {
          percentLoaded = Math.round((e.loaded / e.total) * 100);
          return functions.onProgress(percentLoaded, percentLoaded === 100 ? 'Finalizing' : 'Uploading', file);
        }
      };
    }
    xhr.setRequestHeader('Content-Type', file.type);
    if (signResult.headers) {
      const signResultHeaders = signResult.headers
      Object.keys(signResultHeaders).forEach(key => {
        const val = signResultHeaders[key];
        xhr.setRequestHeader(key, val);
      })
    }
    xhr.setRequestHeader('x-amz-acl', 'public-read');
    this.httprequest = xhr;
    return xhr.send(file);
  };

  handleChange = ({ file, fileList }) => {
    const functions = this.stepFunctions()
    functions.preprocess(file)
    if (!file) {
      functions.onError('CORS not supported', file);
    } else {
      file.onload = () => {
        if (file.status === 200) {
          functions.onProgress(100, 'Upload completed', file);
          return functions.onFinishS3Put('potatopotato', file);
        } else {
          return functions.onError('Upload error: ' + file.status, file);
        }
      };
      file.onerror = () => {
        return functions.onError('XHR error', file);
      };
      file.upload.onprogress = (e) => {
        let percentLoaded;
        if (e.lengthComputable) {
          percentLoaded = Math.round((e.loaded / e.total) * 100);
          return functions.onProgress(percentLoaded, percentLoaded === 100 ? 'Finalizing' : 'Uploading', file);
        }
      };
    }
    console.log('File: ', file)
    // always setState
    this.setState({ fileList });
  }

  render() {
    const props = {
      onChange: this.handleChange,
      multiple: true,
      name: "uploadFile",
      defaultFileList: this.initialState.fileList,
      data: this.uploadFile,
      listType: "text",
      customRequest: ????,
      showUploadList: {
        showPreviewIcon: true,
        showRemoveIcon: true
      },
      onProgress: ( {percent} ) => {
        this.setState({ fileUploading: true })
        console.log('Upload progress: ' + percent + '% ' );
      },
      onError: (error, body) => {
        this.setState({ fileUploading: false })
        console.log("Upload error: " + error);
      },
      onSuccess: (body)=> {
        console.log(body)
        return console.log('base.onFinishS3Put()');
      }
    };

    return (
      <Upload {...props} fileList={this.state.fileList}>
        <Button>
          <Icon type="upload" /> Upload
        </Button>
      </Upload>
    )
  }
}

我知道这段代码是一团糟,没有意义并且到处都是重复的数据。我希望它让它工作,然后清理/优化。基本上,onChange当我尝试使用customRequest. 什么时候customRequest叫?这个解释不是很丰富……不明白它是怎么做的Ajax上传的替换。

3个回答

我也在为此苦苦挣扎,然后我找到了你的问题。

所以我发现使用 customRequest 和 onChange 的方式是:

    <Upload name="file" customRequest={this.customRequest} onChange={this.onChange}>
      <Button>
        <Icon type="upload" /> Click to Upload
      </Button>
    </Upload>

  ...


  onChange = (info) => {
    const reader = new FileReader();
    reader.onloadend = (obj) => {
      this.imageDataAsURL = obj.srcElement.result;
    };
    reader.readAsDataURL(info.file.originFileObj);

    ...
  };

  ...

  customRequest = ({ onSuccess, onError, file }) => {
    const checkInfo = () => {
      setTimeout(() => {
        if (!this.imageDataAsURL) {
          checkInfo();
        } else {
          this.uploadFile(file)
            .then(() => {
              onSuccess(null, file);
            })
            .catch(() => {
              // call onError();
            });
        }
      }, 100);
    };

    checkInfo();
  };

可能有更好的方法来做到这一点,但我希望对你有所帮助。

我为此苦苦挣扎,并找到了一种有效的方法来处理这种情况。

首先 - 只有当您需要更改正文和请求类型(例如使用 post 而不是 'put' 或使用 xml 或添加另一个额外的标头)时,您才应该使用 customRequest 。

对于签名 Url,您可以发送 action prop 回调,它会返回一个带有要上传的正确 Url 的Promise,例如:

 handleUplaod = (file: any) => {
return new Promise(async (resolve, reject) => {
  const fileName = `nameThatIwant.type`;
  const url = await S3Fetcher.getPresignedUrl(fileName);
  resolve(url);
});

并渲染如下:

render(){
    return(
    ....
    <Upload
    action={this.handleUplaod}
    ....
 Upload>

上传者从动作props中获取网址。

提供的 onChange 方法也将在上传状态更改时调用 -

onChange# 当上传正在进行、完成或失败时将调用该函数。

当上传状态改变时,它返回:

{ file: { /* ... / }, fileList: [ / ... / ], event: { / ... */ }, }

上传开始时,您需要从中激活文件阅读器。喜欢:

....
 fileReader = new FileReader();
 .....

onChange = (info) => {
    if (!this.fileReader.onloadend) {
      this.fileReader.onloadend = (obj) => {
        this.setState({
          image: obj.srcElement.result, //will be used for knowing load is finished.
        });
      };
    // can be any other read function ( any reading function from
    // previously created instance can be used )
    this.fileReader.readAsArrayBuffer(info.file.originFileObj);
    }
  };

请注意当完成阶段 event=undefin

要从上传事件更新 UI,您应该使用来自 customRequest 的选项变量,并在需要时调用它们。

onSuccess- 应在您完成上传后调用,它将加载图标更改为文件名。

onError- 将归档的文件名涂成红色。

onProgress- 将更新进度条,应使用 {percent: [NUMBER]} 调用以进行更新。

例如在我的代码中-

customRequest = async option => {
  const { onSuccess, onError, file, action, onProgress } = option;
  const url = action;

  await new Promise(resolve => this.waitUntilImageLoaded(resolve)); //in the next section 
  const { image } = this.state; // from onChange function above
  const type = 'image/png';
  axios
    .put(url, Image, {
      onUploadProgress: e => {
        onProgress({ percent: (e.loaded / e.total) * 100 });
      },
      headers: {
        'Content-Type': type,
      },
    })
    .then(respones => {
      /*......*/
      onSuccess(respones.body);
    })
    .catch(err => {
      /*......*/
      onError(err);
    });
};

 waitUntilImageLoaded = resolve => {
  setTimeout(() => {
    this.state.image
      ? resolve() // from onChange method
      : this.waitUntilImageLoaded(resolve);
  }, 10);
};

我使用 axios 但你也可以使用其他库和最重要的部分 -

render(){
return(
....
<Upload
onChange={this.onChange}
 customRequest={this.customRequest}
...>
  onCustomRequest = file => {
    return new Promise(((resolve, reject) => {
      const ajaxResponseWasFine = true;

      setTimeout(() => {
        if (ajaxResponseWasFine) {
          const reader  = new FileReader();

          reader.addEventListener('load', () => {
            resolve(reader.result);
          }, false);

          if (file) {
            reader.readAsDataURL(file);
          }
        } else {
          reject('error');
        }
      }, 1000);
    }));
  };
        <Dragger
          action={this.onCustomRequest}
          fileList={fileList}
          onChange={this.handleChangeUpload}
          className={styles.dragWrapper}
          showUploadList={true}
          beforeUpload={this.beforeUpload}
        >