Реактивный хук useState внутри функции не работает - PullRequest
2 голосов
/ 07 октября 2019

Я пытаюсь выполнить рефакторинг компонента обратного отсчета, который есть в проекте, над которым я работаю.

Когда я закончил миграцию логики, значение счетчика не сработало. Я решил начать с нуля в 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 после первого запускаинтервала.

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

Ответы [ 2 ]

1 голос
/ 07 октября 2019

Вы можете использовать версию функции Functional Updates , возвращаемую useState, для вычисления нового состояния на основе предыдущего состояния.

Ваш обновленный код будет выглядеть следующим образом:

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);
1 голос
/ 07 октября 2019

Добавьте переменную counter во второй параметр (массив) функции useEffect. Когда вы передаете пустой массив, он будет обновлять 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);

Для получения дополнительной информации об этой реализации, прочитайте о React Hooks и пропускающих эффектах в https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...