简体   繁体   English

在 function 中反应钩子 useState 的设置器不起作用

[英]React hook useState's setter inside function doesn't work

I'm trying to do a refactor of a countdown component that a project I'm working on has.我正在尝试对我正在从事的项目的倒计时组件进行重构。

When I finished the migration of the logic the value of the counter didn't work.当我完成逻辑的迁移时,计数器的值不起作用。 I decided to start from zero in codesandbox, so I tought of the simplest implementation and came out with this:我决定在代码沙箱中从零开始,所以我采用了最简单的实现并得出了这个:

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);

What it happens here is that the value of counter stays on 59 after the first run of the interval.这里发生的情况是,在第一次运行间隔后, counter的值保持在 59。

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

Second iteration on issue关于问题的第二次迭代

Thank you for the response Ross, but the real issue happens when I link the countdown to a handler:感谢罗斯的回复,但是当我将倒计时链接到处理程序时,真正的问题发生了:

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);

Add the counter variable within the second parameter (the array) of the useEffect function.useEffect function 的第二个参数(数组)中添加counter变量。 When you pass in an empty array, it will only update the state once on the initial render.当您传入一个空数组时,它只会在初始渲染时更新一次state An empty array is often used when you're making an HTTP request or something along those lines instead.当您发出 HTTP 请求或类似的东西时,通常会使用空数组。 (Edited for second iteration) (为第二次迭代编辑)

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);

For more information on this implementation, read about React Hooks and skipping effects at https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects .有关此实现的更多信息,请阅读https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects中的 React Hooks 和跳过效果。

You can use the Functional Updates version of the function returned by useState to compute the new state based on the previous state.您可以使用 useState 返回的 function 的功能更新版本,在之前的 state 的基础上计算新的 state。

Your updated code would look like this:您更新后的代码如下所示:

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);

UPDATE EDIT Here is a version that starts the countdown with a click handler:更新编辑这是一个使用点击处理程序开始倒计时的版本:

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);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM