【问题标题】:Emulate the second parameter of setState in a useState environment. Let something happen after setState has occured在 useState 环境中模拟 setState 的第二个参数。在 setState 发生后让某些事情发生
【发布时间】:2020-05-14 20:16:12
【问题描述】:

在之前的 react 版本中,我们可以在状态改变后执行代码,方法是:

setState( prevState => {myval: !prevState.myval}, () => {
    console.log("myval is changed now");
)

并确保第二个“位”仅使用更新的代码执行。现在,其中一个示例描述了可以使用React.useEffect 实现“类似”的东西:

const [myval, setMyval] = React.useState(false);
React.useEffect(() => {
    console.log("myval is changed now");
}

//somewhere
setMyval(o => !o);

虽然这看起来不错,但在我的情况下并没有帮助:在我的情况下,“需要发生什么效果”取决于状态发生变化的位置,最初:

//somewhere
setState( prevState => {myval: !prevState.myval}, () => {
    console.log("myval is changed now");
)
//somewhere else 
setState( prevState => {myval: !prevState.myval}, () => {
    console.log("myval has changed somewhere else");
)

将这些更改为useState + setMyval 会在两个位置触发相同的效果,因此我们无法在 useEffect 中推导出实际需要发生的操作。

如何在基于钩子的函数组件中执行上述操作?


一个更好的用例是一个有两个按钮的屏幕,一个用于加载上一个,另一个用于加载下一个。当isLoading 状态为真时按钮被禁用并且仅在加载时执行操作。为了确保只发生一次加载,我们不能只是“采取行动”,就好像状态立即发生变化:状态变化是异步的,因此可能会发生竞争条件。

在改变状态时使用回调可以防止这种竞争条件,并确保只发生一次加载:

class SomScreen extends React.component {
    state = { isLoading: false }
    render() {
        <div>
            <button 
                disabled={this.state.isLoading} 
                onclick={(e) => {
                    if (!this.state.isLoading) {
                            this.setState((p) => ({isLoading: true}), () => {
                            await fetchPreviousDataAndDoSomethingWithIt()
                            setState({isLoading: false});
                        });
                    }
                }
            />Previous</button>
            <button 
                disabled={this.state.isLoading} 
                onclick={(e) => {
                    if (!this.state.isLoading) {
                        this.setState((p) => ({isLoading: true}), () => {
                            await fetchNextDataAndDoSomethingWithIt()
                            setState({isLoading: false});
                        });
                    }
                }
            />Next</button>
        </div>
    }
}

【问题讨论】:

    标签: reactjs state react-hooks


    【解决方案1】:

    “正在运行的异步操作”问题是“正在运行的这些异步操作中的任何一个”的简化问题,因此您可以分别对每个异步操作进行建模,然后计算 isLoading。像这样的自定义钩子可能会起作用:

    function useAsyncAction(asyncAction) {
      const [isLoading, setIsLoading] = useState(false);
      const callAsyncAction = useCallback(async () => {
        setIsLoading(true);
        await asyncAction();
        setIsLoading(false);
      }, []);
      return [
        isLoading,
        callAsyncAction,
      ]
    }
    
    //
    
    function SomScreen() {
      const [asyncAIsLoading, callAsyncA] =
        useAsyncAction(fetchPreviousDataAndDoSomethingWithIt);
      const [asyncBIsLoading, callAsyncB] =
        useAsyncAction(fetchNextDataAndDoSomethingWithIt);
      const isLoading = asyncAIsLoading || asyncBIsLoading
    
      ...
    }
    

    【讨论】:

    • 虽然这最初可以工作,但进一步的问题 - 如果isLoading 为真,则阻止该操作发生不会正常工作吗? - 由于每个 asyncAction 都必须查看彼此的操作,或者相反,“更全局”isLoading 只会在下一次重新渲染时设置。我想我可以使用它,因为不希望在单个框架内有多个用户输入,但它似乎不正确。
    • &lt;button disabled={isLoading}&gt; 可以多次使用以禁用按钮。
    • 如果您正在寻找渲染保证,即使setState 也无法保证组件在@987654327 之后确切何时重新渲染@调用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-04
    • 1970-01-01
    • 2018-12-23
    相关资源
    最近更新 更多