【问题标题】:State objects and child component using sub values of object使用对象的子值状态对象和子组件
【发布时间】:2020-10-20 17:37:59
【问题描述】:

我有一个允许多个并发上传的 Dropzone,我想显示所有上传的进度。

在我的 Dropzone 组件中,我有一部分状态是上传数组:

const [uploads, setUploads] = useState([])

uploads 数组的每个元素都是一个包含几个值的上传对象,如下所示:

const uploading = {
  id: 1,
  files: <array of files>,
  progress: 0
}

一旦文件/文件夹被拖放到 dropzone 中,“uploading”对象将被添加到“uploads state array”中,并且文件将被发送到后端 API,后端 API 将文件异步上传到服务器。

后端会定期向 UI 发送进度回调,以更新上传状态数组中正确元素的进度值(请参阅下面的 progressCallback)

我目前无法实现的是确保每次上传数组中的对象被更新以显示进度时 UI 都会重新渲染,这样我就可以在所有上传发生时显示它们的进度。

UI 组件如下所示:

export function UploaderDropzone(props) {
  const [uploads, setUploads] = useState([])

  const progressCallback = useCallback((progressObject, sessionContext, step) => {
    const {uploadSessionParameters} = sessionContext
    let uploading = {}
    // let tmpArray = []
    const tmpArray = [...uploads]

    if (step === 'progress') {
      const filtered = findUploadById(tmpArray, uploadSessionParameters.uploadSessionId)
      uploading = filtered[0]

      if (uploading) {
        const itemIndex = tmpArray.indexOf(uploading)
        tmpArray.splice(itemIndex, 1)

        uploading.progress = progressObject.percentUpload

        tmpArray.push(uploading)
        setUploads(tmpArray)
        // setUploads(prevUploads => [...prevUploads, uploading])
      }

      console.log('progress tmpArray = ' + JSON.stringify(tmpArray));
      console.log('progress uploads = ' + JSON.stringify(uploads))
    }

    if (step === 'initialize') {
      const uploadNumber = uploads.length + 1

      uploading = {
        uploadSessionId: uploadSessionParameters.uploadSessionId,
        files: sessionContext.files,
        uploadNumber: uploadNumber,
        uploadName: `Upload #${uploadNumber}`,
        sent: false,
        progress: 0,
      }

      tmpArray.push(uploading)
      setUploads(tmpArray)

      console.log('initialize tmpArray = ' + JSON.stringify(tmpArray))
      console.log('initialize uploads = ' + JSON.stringify(uploads))
    }
  }, [uploads])

  const progressBars = uploads.map((upload) => {
    return (
      <Fragment>
        <ProgessBar progress={upload.progress} />
      </Fragment>
    )
  })

  // ... more code here ... not required for understanding

  return {
    <Fragment>
      <Dropzone 
        onDrop={
          acceptedFiles => {
            const filteredFiles = acceptedFiles.filter((file) => 
              validateFile(file))

              console.log("Filtered files" + filteredFiles)

              if (filteredFiles.length > 0) {
                setAlertMsg('')
              }
              else {
                setAlertMsg('No files uploaded.')
              }

              // call to Node.js backend, passing it the progressCallback
              startAsyncUploadSession(filteredFiles, progressCallback);
          }
        }
      />
      {progressBars}
    </Fragment>
    
  }

}

ProgressBar 组件非常简单:

export function ProgressBar(props) {
  const {progress} = props

  return (
    <Fragment>
      <p>`${progress}% uploaded ...`</p>
    </Fragment>
  )
}

现在,即使上传状态数组在 progressCallback 中不断更新,这段代码甚至都不会显示进度条。由于我不知道将要完成的并发上传数量,因此我无法在高阶组件中设置状态并将其作为道具传递,我需要子组件(ProgressBar)将其作为道具从多个对象中接收状态数组......但我显然错过了一些东西......

任何指针?我可以使用任何挂钩来在上传状态数组中注册对象的进度值,以便每次后端更新我们的进度时,它都会反映在 UI 中?

编辑:包含@Robin Zigmond 建议的部分修复

Edit2:经过一些调试,似乎是同步问题。所以我需要在这里添加一些代码和细节。

当文件被拖放到 Dropzone 时,它​​通过对模拟服务器的函数调用将文件发送到 Node.js 后端,调用 startAsyncUploadSession(filteredFiles, progressCallback);在 Dropzone 的 onDrop 事件中(使用 react-dropzone 库)。

似乎当我稍后调用 progressCallback 时,状态与第一次渲染时一样,也就是上传状态数组是一个空数组,就像文件被删除时一样,而不是包含添加对象的更新数组在“初始化”步骤中上传数组。

因此修改后的问题是“如何确保在稍后后端调用 progressCallback 时 UI 状态是最新的?”

【问题讨论】:

    标签: javascript reactjs react-native


    【解决方案1】:

    问题在于progressCallback 中的状态更新代码。这是有问题的代码,供参考:

    const tmpArray = uploads
    const itemIndex = tmpArray.indexOf(uploading)
    tmpArray.splice(itemIndex, 1)
    
    // HERE UPDATING ONE OF ITEM'S VALUES IN UPLOADS STATE ARRAY
    uploading.progress = progressObject.percentUpload
    
    tmpArray.push(uploading)
    setUploads(tmpArray)
    

    这是做什么的:

    1. tmpArray 设置为对与当前状态相同的对象 (uploads) 的引用
    2. 然后改变该数组,首先通过拼接一个元素,然后将一个新元素推到它上面

    在步骤 2) 中的任何时候,引用都不会改变。因此,当您随后调用 setUploads(tmpArray) - 也可能是 setUploads(uploads) 因为这两个变量仍然引用完全相同的数组时 - React 认为您正在将状态设置为完全一样,因此不会知道要重新渲染。

    这就是解释为什么你永远不应该改变状态的漫长方式,就像你在这里所做的那样。您需要不可变地更新它——也就是说,不理会旧状态,构造一个新对象/数组,并将其传递给设置新状态的函数。

    这里有很多方法可以做到这一点,但在您的情况下,它应该像将tmpArray 制作为当前状态的(浅)*副本一样简单。也就是改变:

    const tmpArray = uploads
    

    const tmpArray = [...uploads]
    

    现在tmpArray 是一个新的引用,指向一个与以前保持相同值的数组。请注意,副本只是“浅层”,因此数组中的对象仍然是对每个数组元素的一个底层对象的引用。但这在这里似乎无关紧要,因为您不会改变这些对象。如果您尝试使用此更改的代码,我相信它应该开始工作(或至少让您克服这个特定问题)。

    【讨论】:

    • 这确实是朝着正确方向迈出的一步......虽然现在我调试了我注意到我还有另一个问题。现在我必须解决的事实是,在分配“const tmpArray = [...upload]”之后,我的 tmpArray 显示为“未定义”......即使在调试时,我也看到上传包含元素。我想知道 tmpArray 怎么可能在该行之后未定义,可能是因为 progressCallback 是由不知道 UI 状态的后端调用的?
    • 不知道这是怎么发生的——[ ...someVar ] 形式的任何东西都将是一个数组(如果它不抛出错误),它不可能是未定义的。不幸的是,我无法为您调试,我看不到您的其余代码或当您单步执行代码时发生了什么 - 但也许您在某处混淆了一些变量名称?
    • 我也不明白这是怎么发生的,老实说,我已经尝试确保 tmpArray 至少有一个空值,如下所示: let tmpArray = []; ...然后为它分配上传的值,如下所示: tmpArray = [...uploads] 它仍然显示为未定义。我在故事书中运行它,我可能需要进一步调试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多