当前位置: 首页 > 知识库问答 >
问题:

如何处理use效应闭包内的陈旧状态值?

丁景山
2023-03-14

下面的示例是一个计时器组件,它有一个按钮(用于启动计时器)和两个标记,显示经过的秒数和经过的秒数乘以2。

但是,它不工作(CodeSandbox演示)

守则

import React, { useState, useEffect } from "react";

const Timer = () => {
  const [doubleSeconds, setDoubleSeconds] = useState(0);
  const [seconds, setSeconds] = useState(0);
  const [isActive, setIsActive] = useState(false);

  useEffect(() => {
    let interval = null;
    if (isActive) {
      interval = setInterval(() => {
        console.log("Creating Interval");
        setSeconds((prev) => prev + 1);
        setDoubleSeconds(seconds * 2);
      }, 1000);
    } else {
      clearInterval(interval);
    }
    return () => {
      console.log("Destroying Interval");
      clearInterval(interval);
    };
  }, [isActive]);

  return (
    <div className="app">
      <button onClick={() => setIsActive((prev) => !prev)} type="button">
        {isActive ? "Pause Timer" : "Play Timer"}
      </button>
      <h3>Seconds: {seconds}</h3>
      <h3>Seconds x2: {doubleSeconds}</h3>
    </div>
  );
};

export { Timer as default };

问题

在使用效果调用中,秒值将始终等于上次呈现使用效果块时(当isActive上次更改时)的值。这将导致setDouble秒(秒*2)语句失败。React Hooks ESLint插件给我一个关于这个问题的警告:

React Hook useEffect缺少依赖项:“秒”。包括它或删除依赖项数组。如果“setDoubleSeconds”需要当前值“seconds”,您还可以使用useReducer替换多个useState变量。(反应钩/详尽的deps)eslint

正确地说,向依赖数组添加“秒”(并将setDouble秒(秒*2)更改为setDouble秒((秒1) * )将呈现正确的结果。然而,这有一个令人讨厌的副作用,即导致间隔在每个渲染上被创建和破坏(console.log(“破坏间隔”)在每个渲染上触发)。

因此,现在我在看ESLint警告中的另一条建议:“如果'setDoubleSeconds'需要当前的'seconds'值,您还可以用useReducer替换多个useStatehtml" target="_blank">变量。”。

我不理解这项建议。如果我创建一个减速器,并像这样使用它:

import React, { useState, useEffect, useReducer } from "react";

const reducer = (state, action) => {
    switch (action.type) {
        case "SET": {
            return action.seconds;
        }
        default: {
            return state;
        }
    }
};

const Timer = () => {
    const [doubleSeconds, dispatch] = useReducer(reducer, 0);
    const [seconds, setSeconds] = useState(0);
    const [isActive, setIsActive] = useState(false);

    useEffect(() => {
        let interval = null;
        if (isActive) {
            interval = setInterval(() => {
                console.log("Creating Interval");
                setSeconds((prev) => prev + 1);
                dispatch({ type: "SET", seconds });
            }, 1000);
        } else {
            clearInterval(interval);
        }
        return () => {
            console.log("Destroying Interval");
            clearInterval(interval);
        };
    }, [isActive]);

    return (
        <div className="app">
            <button onClick={() => setIsActive((prev) => !prev)} type="button">
                {isActive ? "Pause Timer" : "Play Timer"}
            </button>
            <h3>Seconds: {seconds}</h3>
            <h3>Seconds x2: {doubleSeconds}</h3>
        </div>
    );
};

export { Timer as default };

陈旧值的问题仍然存在(CodeSandbox演示(使用减缩器))。

问题

那么这个场景的推荐是什么呢?我是否接受性能打击,简单地向依赖数组添加“秒”?我是否创建了另一个依赖于"秒"并在其中调用"setDouble秒()"的use效应块?是否将“秒”和“双秒”合并到单个状态对象中?我使用裁判吗?

此外,您可能会想“为什么不简单地更改

谢谢


共有2个答案

危宜
2023-03-14
  • 我是否需要性能命中并简单地向依赖数组添加“秒”?
  • 我是否创建了另一个依赖于秒并在那里调用setDouble秒()的use效应块?
  • 是否将秒和双秒合并到单个状态对象中?
  • 我使用参考文献吗?

尽管我个人更愿意选择第二种方法,但它们都能正常工作:

useEffect(() => {
    setDoubleSeconds(seconds * 2);
}, [seconds]);

然而:

在我的实际应用程序中,doubleSeconds被传递给子组件,我不希望子组件知道如何将seconds映射到doubleSeconds,因为这会降低子组件的可重用性

这是值得怀疑的。子组件可以实现如下:

const Child = ({second}) => (
  <p>Seconds: {second}s</p>
);

和父组件应如下所示:

const [seconds, setSeconds] = useState(0);
useEffect(() => {
  // change seconds
}, []);

return (
  <React.Fragment>
    <Child seconds={second} />
    <Child seconds={second * 2} />
  </React.Fragment>
);

这将是一种更加清晰和简洁的方式。

章哲茂
2023-03-14
匿名用户

您可以通过几种方式访问效果回调中的值,而无需将其添加为dep。

  1. 设置状态。您可以通过状态变量的setter点击状态变量的最新值
setSeconds(seconds => (setDoubleSeconds(seconds * 2), seconds));
const secondsRef = useRef(0);
const [seconds, setSeconds] = useReducer((_state, action) => (secondsRef.current = action), 0);

然后可以使用secondsRef。当前访问代码块中的秒,而不触发deps更改。

setDoubleSeconds(secondsRef.current * 2);

在我看来,你永远不应该从deps数组中省略依赖项。如果你需要deps不改变,使用像上面这样的黑客来确保你的值是最新的。

首先要考虑的是,是否有一些更优雅的方式编写代码,而不是将值放入回调中。在您的示例中,doubleSeconds可以表示为seconds的导数。

const [seconds, setSeconds] = useState(0);
const doubleSeconds = seconds * 2;

有时候应用程序并不那么简单,所以你可能需要使用上面描述的黑客。

 类似资料:
  • 问题内容: 我正在尝试将事件发射器与React 和一起使用,但是它始终获取初始状态而不是更新状态。如果我直接调用事件处理程序(即使使用),也可以使用。 如果我将值传递给第二个参数,它将使其起作用,但是,每当值更改时(这是击键触发的),这都会导致事件发射器重新订阅。 我究竟做错了什么?我试过,,,和,并不能得到任何工作。 这是复制品: 这是具有相同代码的代码沙箱: https://codesandb

  • 我一直在尝试创建一个,它也有一个处理程序。我阅读了这个问题的答案并知道如何做到这一点。 我的问题只是关于它的结束部分。 1) 我知道我可以写:或,但我不会写。在这里的注释中解释了这一点,因为您需要处理参数操作。 这是否意味着由于处理程序是

  • 我正在使用驱动程序。findelements(By.xpath(“//*);要检索页面上的所有元素,但是在IE9/IE10上,我得到了一个陈旧的元素异常。我如何处理它?请帮助我。 错误如下所示:

  • 在Autoconf的第2版,大部分宏被重新命名以使用更加统一和具有描述性的命名方案。下面是被重新命名了的宏的原来名字, 随后给出了这些宏现在的名字。虽然为了保持向后兼容,旧名字仍然能够被autoconf程序所接受,旧名字都 被看作过时的。关于新的命名方案,参见 宏名 。AC—ALLOCAAC—FUNC—ALLOCA AC—ARG—ARRAY 因为用途有限而被删除了。AC—CHAR—UNSIGNED

  • 我们正在将AngularJS应用程序迁移到最新的Angular版本。我们总是通过接收状态代码200来处理http请求,如果有错误,我们仍然得到状态代码200(不包括404-500,以及关于实际服务器或用户连接的错误)。例如,如果用户令牌过期,我们发出请求,我们将得到一个状态代码200,其主体为errorcode:number,message:string。这是不会改变的。 使用关于令牌的相同示例,

  • 主要内容:追踪Poll,其实真正处理响应是在 Networkclient的poll,步骤三追踪Poll 从poll里面进入slector的poll调用addToStagedReceives()进行消息处理(把接收的数据加入到待处理队列); 相应数据结构 selector的poll() addToCompletedReceives(),把响应存入到completedReceives 其实真正处理响应是在 Networkclient的poll,步骤三 调用 handleCompletedR