从 catch() 函数调用的 setSomeState() 不更新

setSomeState() called from a catch() function doesn't update

如果 React 代码有问题,出于某种原因,当我从 MyApp.promise().then(<here>) 更新 UI 而不是我的 MyApp.promise().then().catch(<here>)[=23 时,一切都按预期工作=]

我想我的问题是:catch() 函数有什么特别之处,所以 React 不会运行?

这是处理升级状态更新的应用程序挂钩代码:

const useUpdateStatus = () => {
    const [data,setData] = useState({status: STATUS.IDLE,changelog:null,tasks:[]})

    const updateData = (d) => {
        // We call setData with an anonymous function so we can merge previous
        // data with new data
        setData((prev) => {
            console.log({ ...prev, ...d })
            return { ...prev, ...d }
        })
    };

    // Only once, we set the timer to periodically update status
    useEffect(() => {
        setInterval(() => {
            MyApp.get('/system/upgrade')
            .then((upgrade) => {
                // If anything is not "pending", it means we are upgrading
                for (var t of upgrade.tasks) {
                    if (t.status !== "pending") {
                        updateData({ status: STATUS.INSTALLING})
                    }
                }
                // updateData will call setData with the full status

                // This works as intended, UI is updated on each step
                updateData({ tasks: upgrade.tasks, changelog: upgrade.changelog})
            })
            .catch((e) => {
                // If data can't be fetched, it probably means we are restarting
                // services, so we updated the tasks array accordingly
                setData((prev) => {
                    for (var t of prev.tasks) {
                        if (t['id'] === "restarting") {
                            t['status'] = 'running'
                        }
                        else if (t['status'] == "running") {
                            t['status'] = 'finished'
                        }
                    }
                    // The expected data is logged here
                    console.log(prev)
                    return prev
                })
            })
        }, 1000);
    },[])
    return data
}

这是表示层:

// Using the hook :
const { changelog, tasks, status } = useUpdateStatus()

// Somewhere int he page :
<UpdateProgress tasks={tasks}/>

// The actual components :
const UpdateProgress = (props) => {
    return(
        <div style={{display: "flex", width: "100%"}}>
            { props.tasks.map(s => {
                return(
                    <UpdateTask key={s.name} task={s}/>
                )
            })}
        </div>
    )
}

const UpdateTask = (props) => {
    const colors = {
        "pending":"LightGray",
        "running":"SteelBlue",
        "finished":"Green",
        "failed":"red"
    }
    return(
        <div style={{ textAlign: "center",  flex: "1" }}>
                <Check fill={colors[props.task.status]} width="50px" height="50px"/><br/>
                <p style={props.task.status==="running" ? {fontWeight: 'bold'} : { fontWeight: 'normal'}}>{props.task.name}</p>
        </div>
    )
}

React 执行 Object.is 比较以检查状态更新调用后是否需要重新渲染。由于您正在改变 catch 块中的状态,因此会错误地通知 React 状态没有改变,因此不会触发重新渲染

您可以像下面这样更新您的状态以使其正常工作

 .catch((e) => {
            // If data can't be fetched, it probably means we are restarting
            // services, so we updated the tasks array accordingly
            setData((prev) => {
                for (var t of prev.tasks) {
                    if (t['id'] === "restarting") {
                        t['status'] = 'running'
                    }
                    else if (t['status'] == "running") {
                        t['status'] = 'finished'
                    }
                }
                // The expected data is logged here
                console.log(prev)
                return {...prev}
            })
        })

然而,更新状态的更好方法是以不可变的方式进行

  .catch((e) => {
            // If data can't be fetched, it probably means we are restarting
            // services, so we updated the tasks array accordingly
            setData((prev) => ({
                ...prev,
                tasks: prev.tasks.map((task) => {
                    if (task.id === "restarting") {
                        return { ...task, status: 'running'}
                    }
                    else if (task.id === "running") {
                        return { ...task, status: 'finished'}
                    }
                    return task
                })
            }))
        })