【问题标题】:SetInterval is not showing updated stateSetInterval 未显示更新状态
【发布时间】:2019-08-13 11:19:17
【问题描述】:

在调用setInterval 函数之前,我已将状态设置为true。但即使 useEffect 钩子被状态的新值触发,它也不会反映在 setInterval 函数中。

这里的代码沙箱:https://jsfiddle.net/6e05tc2L/3/

let interval;
const Component = () => {
  React.useEffect(() => {
    console.log('State updated to', state);
  });
  const [state, setState] = React.useState(false);
  const on = () => {
    setState(true);
    interval = setInterval(() => {
      console.log(state);
    }, 1000);
    }
  const off = () => {
    setState(false);
    clearInterval(interval);
  }
  const toggle = () => state ? off() : on()

  return (<div>
    <button onClick={toggle}>Toggle State</button>
   </div>);
}

ReactDOM.render(
  <Component />,
  document.getElementById('container')
);

它不应该在更新后使用更新的 state 值吗?

【问题讨论】:

  • 传递给setInterval 的函数在on 被调用时只创建了1 次,它在创建时关闭了state 的值。未来的渲染再次调用React.useState 并“看到”一个新的state,但是在调用on 时创建的函数基本上停留在过去:它在创建时关闭了state,并且永远不会得到新的state 的值。
  • 因此,如果您查看 Abramov 的 useInterval,它会在每次渲染时调用 useEffect,并使用新版本的回调更新引用的 .current,该回调的值超过 state 的值那个渲染调用。传递给本机 setInterval 的函数永远不会改变,但该函数所做的只是调用 另一个 函数,该函数的引用实际上正在在每次渲染时更新。
  • @RossAllen 因此回调函数由于使用了 useRef 而发生了变化,但 tick 函数保持不变。这很棒,但感觉很复杂。谢谢! :)
  • 是的,没错。 tick 永远不会改变,但是在您自己调用 useInterval 时,您会在每次渲染时传入一个新函数。该函数被设置为savedCallback.current 并由永久tick 调用。

标签: javascript reactjs setinterval react-hooks


【解决方案1】:

你传递给 useEffect 的函数内部的值在每次渲染时都会刷新,因为 useEffect 使用你传递给它的函数的新定义。

但是传递给 setInterval 的函数只定义了一次,它会关闭 state 的旧值。哪个还没有更新。

钩子的闭包很棘手,但需要意识到的是,useEffect 会为每次渲染创建一个新函数,因此每次函数关闭时都会有一个新的状态值。

诀窍是在 useEffect 本身内部调用与 setInterval 相关的代码,这本身取决于 state 的变化值

React.useEffect(() => {
  if(state) {

    interval = setInterval(() => {
      console.log(state);
    }, 1000);
  } else {
    clearInterval(interval);
  }

}, [state]);

或者,更好的是,使用 useInterval 钩子来为您处理这些细节。

【讨论】:

  • 如果我们使用局部变量,那么即使变量在函数范围之外,它也会使用变量的更新值。钩子状态与变量的闭包有何不同以及为什么不同?
  • 关闭意味着关闭函数范围之外的值。这种差异不是因为范围。这是因为一旦函数关闭一个值,它总是引用该变量。这就是 setInterval 回调发生的情况。它被定义一次,并依附于 state 的任何值。但是你传递给 useEffect 的函数在每次渲染时都会被丢弃,并创建一个新的函数,然后关闭变量中的最新值。
  • 接受了这个答案,因为它显示了一个简单的解决方法。我所说的变量的意思是假设我们有一个全局声明的单独的计数器变量,那么如果我们使用 setInterval 来增加它,那么它确实会增加它而不在 useEffect 钩子下定义它。
  • 但这是全球性的。并保持在范围内,直到模块退出或您的程序退出。 state 在您的情况下,一旦渲染功能完成运行,就会超出范围。只有在该函数内部定义的函数才能通过闭包访问该变量。
  • 有道理!没想到是这样!我对这个问题有了一些“结论”! :)
【解决方案2】:

setInterval 始终可以访问组件的 first 渲染的值,因为传递给 setInterval 的函数围绕该值关闭并且永远不会重新声明。您可以使用自定义挂钩来解决此问题:

function useInterval(callback, delay) {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    let id = setInterval(tick, delay);
    return () => clearInterval(id);
  }, [delay]);
}

React Hooks 和 setInterval 之间的不匹配的实现和详尽解释来自 Making setInterval Declarative with React Hooks,作者是 React 贡献者之一 Dan Abramov。

【讨论】:

  • 为 Dan 的文章点赞!但它并没有解释为什么它会发生,但它提供了一个很好的解决方法。
【解决方案3】:

我不是 ReactJS 专家,但我猜您正在记录的 state 没有刷新,因为它被声明一次并且从未刷新。如果React.useState(false) 是给你状态的方法,你应该在你的区间函数中使用它。

这是我试图解释的一个例子:

const object = { value: false }


const notRefreshed = object.value // here we are copying the value

const interval = setInterval(() => {
  const refreshed = object.value // here we are using the reference to copy the latest value
  console.log("refreshed", refreshed)
  console.log("notRefreshed", notRefreshed)
}, 500)


setTimeout(() => object.value = true, 1600)

setTimeout(() => clearInterval(interval), 2600)

【讨论】:

    【解决方案4】:

    如果你想在你的状态改变时重新加载你的组件,你应该像这样创建你的 useEffect。

    React.useEffect(() => {
        console.log('State updated to', state);
    }, [state]);
    

    您创建的方式与componentDidMount() 相同,将数组作为第二个参数,就像componentDidUpdate() 具有它的依赖项一样。因此,只要您的状态发生变化,您的组件就会重新渲染。

    要解决 setTimeout 的无限调用,您可以在创建函数的地方执行此操作

    React.useCallback(() => {
        setInterval(() => {
            console.log(state);
        }, 1000);
    })
    

    有了这个 React 就会知道你只想创建这个函数一次。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-01
      相关资源
      最近更新 更多