【问题标题】:React hook useState's setter inside function doesn't workReact hook useState 的 setter inside 函数不起作用
【发布时间】:2019-10-07 20:18:41
【问题描述】:

我正在尝试对我正在处理的项目中的倒计时组件进行重构。

当我完成逻辑迁移时,计数器的值不起作用。我决定在 Codesandbox 中从零开始,所以我选择了最简单的实现并得出了这个:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  const [counter, setCounter] = useState(60);

  useEffect(() => {
    const interval = setInterval(() => setCounter(counter - 1), 1000);

    return () => clearInterval(interval);
  }, []);

  return (
    <div className="App">
      <h1>Hello CodeSandbox {counter}</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App timerSeconds={360} />, rootElement);

这里发生的情况是counter 的值在第一次运行间隔后保持在 59。

代码沙盒:https://codesandbox.io/embed/flamboyant-moon-ogyqr

关于问题的第二次迭代

感谢罗斯的回复,但真正的问题发生在我将倒计时链接到处理程序时:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  const [counter, setCounter] = useState(60);
  const [countdownInterval, setCountdownInterval] = useState(null);


  const startCountdown = () => {
    setCountdownInterval(setInterval(() => setCounter(counter - 1), 1000));
  };

  useEffect(() => {
    return () => clearInterval(countdownInterval);
  });

  return (
    <div className="App" onClick={startCountdown}>
      <h1>Hello CodeSandbox {counter}</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App timerSeconds={360} />, rootElement);

【问题讨论】:

    标签: reactjs react-hooks


    【解决方案1】:

    useEffect 函数的第二个参数(数组)中添加counter 变量。当您传入一个空数组时,它只会在初始渲染时更新一次state。当您发出 HTTP 请求或类似的东西时,通常会使用空数组。 (为第二次迭代编辑)

    import React, { useEffect, useState } from "react";
    import ReactDOM from "react-dom";
    
    import "./styles.css";
    
    function App() {
      const [counter, setCounter] = useState(5);
      const [counterId, setCounterId] = useState(null);
    
      useEffect(() => {
        return () => clearInterval(counterId);
      }, []);
    
      const handleClick = () => {
    
        /* 
         * I'd take startCountdown and make
         * it's own component/hook out of it, 
         * so it can be easily reused and expanded.
         */
        const startCountdown = setInterval(() => {
          return setCounter((tick) => {
            if (tick === 0) {
              clearInterval(counterId);
              setCounter(0);
              return setCounterId(null);
            };
    
            return tick - 1;
          });
        }, 1000)
    
        setCounterId(startCountdown);
      };
    
      return (
        <div className="App" onClick={handleClick}>
          <h1>Hello CodeSandbox {counter}</h1>
          <h2>Start editing to see some magic happen!</h2>
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App timerSeconds={360} />, rootElement);
    

    有关此实现的更多信息,请在 https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects 阅读有关 React Hooks 和跳过效果的信息。

    【讨论】:

    • 我将此设置为答案,因为这解决了我的第一个问题,但我仍然遇到此问题。刚刚更新了评论并添加了第二次迭代。
    • 只有一件事@Ross Sheppard,id 从未设置过,应该在组件卸载时清除间隔。倒计时有效,但为了性能。我试图将计时器 ID 保留在状态变量上,但倒计时继续工作:codesandbox.io/s/keen-beaver-puuus
    【解决方案2】:

    您可以使用 useState 返回的函数的Functional Updates 版本,根据之前的状态计算新的状态。

    您更新后的代码如下所示:

    import React, { useState, useEffect } from "react";
    import ReactDOM from "react-dom";
    
    import "./styles.css";
    
    function App() {
      const [counter, setCounter] = useState(60);
    
      useEffect(() => {
        const interval = setInterval(() => {setCounter(counter => counter - 1);}, 1000);
    
        return () => clearInterval(interval);
      }, []);
    
      return (
        <div className="App">
          <h1>Hello CodeSandbox {counter}</h1>
          <h2>Start editing to see some magic happen!</h2>
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App timerSeconds={360} />, rootElement);
    

    更新编辑 这是一个使用点击处理程序开始倒计时的版本:

    import React, { useState, useEffect } from "react";
    import ReactDOM from "react-dom";
    
    import "./styles.css";
    
    function App() {
      const [counter, setCounter] = useState(60);
      const [countdownInterval, setCountdownInterval] = useState(null);
    
    
      const startCountdown = () => {
        setCountdownInterval(setInterval(() => setCounter(counter => counter - 1), 1000));
      };
    
      useEffect(() => {
        return () => clearInterval(countdownInterval);
      }, [countdownInterval]);
    
      return (
        <div className="App" onClick={startCountdown}>
          <h1>Hello CodeSandbox {counter}</h1>
          <h2>Start editing to see some magic happen!</h2>
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App timerSeconds={360} />, rootElement);
    

    【讨论】:

    • 这也是一个有效的解决方案,但是当您将setInterval 链接到处理程序时,任何选项都会停止工作。
    • 这不仅仅是另一个有效的解决方案。这是唯一正确的解决方案。
    猜你喜欢
    • 2021-11-27
    • 1970-01-01
    • 2019-10-21
    • 2020-03-18
    • 2019-09-14
    • 1970-01-01
    • 2019-12-02
    • 1970-01-01
    相关资源
    最近更新 更多