React - использование хуков внутри setTimeout - PullRequest
0 голосов
/ 20 июня 2020

У меня есть простая программа, которая генерирует массив. Я добавил функцию реверсирования массива при нажатии кнопки, но когда я нажимаю на реверс, отображаемый массив не перевернется, а останется как есть. Я использую setTimeout, чтобы его можно было визуализировать. Почему не происходит визуализация?

Вот мой код:

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

const Dummy2 = () => {
    const [arr, setArr] = useState([]);
    const [length, setLength] = useState(10);

    useEffect(() => {
        generateArray();
    }, [length]);

    const generateArray = () => {
        const temp = [];
        for(let i = 0; i < length; i++) {
            temp.push(i + 1);
        }
        setArr(temp);
    }

    const handleLength = (e) => {
        setLength(e.target.value);
    }

    const reverseArray = () => {
        let delay = 1;
        for(let i = 0; i < length / 2; i++) {
            setTimeout(() => {
                const temp = arr;
                const t1 = arr[i];
                const t2 = arr[length - 1 - i];
                temp[i] = t2;
                temp[length - 1 - i] = t1;
                setArr(temp);
            }, delay * 1);
            delay++;
        }
    }

    const printArray = () => {
        console.log(arr);
    }

    const maxVal = Math.max(...arr);
    
    return (
        <div>
            <div className="array-container" style={{height: '50%'}}>
                {arr.map((value, idx) => (
                    <div className="array-element"
                         key={idx}
                         style={{height: `${(value * 100 / maxVal).toFixed()}%`,
                                 width: `calc(${100 / length}% - 2px)`,
                                 margin: '0 1px',
                                 display: 'inline-block',
                                 backgroundColor: 'black',
                                 color: 'white'}}
                    >{value}</div>))
                }
            </div>
            <div>
                <button onClick={() => generateArray()}>New array</button>
                <button onClick={() => reverseArray()}>Reverse</button>
                <button onClick={() => printArray()}>Print array</button>
            </div>
            <div className="slider-container">
                1
                <input type="range" 
                       min="1" 
                       max="100" 
                       onChange={(e) => handleLength(e)} 
                       className="slider" 
                       id="myRange" 
                />
                100
            </div>
            {length}
        </div>
    );
}

export default Dummy2;

Ответы [ 2 ]

1 голос
/ 20 июня 2020

В функции reverseArray вы устанавливаете состояние с каждой итерацией, которая вызывает ошибку.

Вам нужно полностью отменить массив, а затем назначить его состоянию.

Измените функцию reverseArray, как показано ниже:

  const reverseArray = () => {
    const temp = [...arr];
    let delay = 1000;
    for (let i = 0; i < length / 2; i++) {
      setTimeout(() => {
        const t1 = arr[i];
        const t2 = arr[length - 1 - i];
        temp[i] = t2;
        temp[length - 1 - i] = t1;
        setArr([...temp]);
      }, delay);
      delay+=1000;
    }
  }

Рабочий пример:

Демо

EDIT

Вставлено timeout для визуализации с 1s разницей во времени на каждой итерации.

0 голосов
/ 20 июня 2020

Вот что я могу вам предложить:

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

const Dummy2 = () => {
  const [arr, setArr] = useState([]);
  const [length, setLength] = useState(10);
  const [reverseIndex, setReverseIndex] = useState(0);
  const [didMount, setDidMount] = useState(true);
  const [shouldReverseArray, setShouldReverseArray] = useState(true);

  useEffect(() => {
    generateArray();
  }, [length]);

  useEffect(() => {
    // After clicking on 'reverse' button, reverseIndex is edited. Thus this effect is triggered because it listen to 'reverseIndex'. DidMount is here to prevent triggering the effect when the component is mounted
    if (!didMount) {
      if (shouldReverseArray && reverseIndex < length / 2) {
        setTimeout(() => reverseArray(), 1000);
      } else {
        shouldReverseArray = setShouldReverseArray(false);
        setReverseIndex(0);
      }
    } else {
      setDidMount(false);
    }

  }, [reverseIndex]);

  const generateArray = () => {
    const temp = [];
    for (let i = 0; i < length; i++) {
      temp.push(i + 1);
    }
    setArr(temp);
  };

  const handleLength = e => {
    setLength(e.target.value);
  };

  const reverseArray = () => {
    const temp = Object.assign([], arr); // Make a copy of your state because it is not mutable
    const tempStart = temp[reverseIndex];
    const tempEnd = temp[length - tempStart];
    temp[reverseIndex] = tempEnd;
    temp[length - tempStart] = tempStart;
    setArr(temp); // Set the new arr
    setReverseIndex(prevState => ++prevState); // Set the new index
  };

  const printArray = () => {
    console.log(arr);
  };

  const maxVal = Math.max(...arr);

  return (
    <div>
      <div className="array-container" style={{ height: "50%" }}>
        {arr.map((value, idx) => (
          <div
            className="array-element"
            key={idx}
            style={{
              height: `${((value * 100) / maxVal).toFixed()}%`,
              width: `calc(${100 / length}% - 2px)`,
              margin: "0 1px",
              display: "inline-block",
              backgroundColor: "black",
              color: "white"
            }}
          >
            {value}
          </div>
        ))}
      </div>
      <div>
        <button onClick={() => generateArray()}>New array</button>
        <button onClick={() => {setShouldReverseArray(true); reverseArray()}}>Reverse</button>
        <button onClick={() => printArray()}>Print array</button>
      </div>
      <div className="slider-container">
        1
        <input
          type="range"
          min="1"
          max="100"
          onChange={e => handleLength(e)}
          className="slider"
          id="myRange"
        />
        100
      </div>
      {length}
    </div>
  );
};

export default Dummy2;

render(<Dummy2 />, document.getElementById("root"));

Вот Stackblitz repro .

Обратите внимание, что при изменении размера вашего массива вести себя странно, я не знаю, исходит ли это из моего кода или вашего, я попытаюсь выяснить.

Кроме того, если вам не нужно отображать каждое изменение, вы можете использовать .reverse() это было бы действительно проще.

[Edit]: Хорошо, ошибка произошла из-за того, что reverseIndex не сбрасывается. Я отредактировал код, чтобы добавить состояние shouldReverseArray. Я вызываю это в событии onClick в jsx. Довольно грязно, но я не могу здесь думать ни о чем другом. Надеюсь, это поможет:)

...