Добавлять элемент в массив и удалять элемент спереди, по одному за раз, каждые 2 секунды, пока массив не станет пустым?

#javascript #arrays #reactjs #react-native

#javascript #массивы #reactjs #реагировать-родной

Вопрос:

Итак, я использую react и у меня есть кнопка, которая добавляет элемент в массив с помощью useState, когда я нажимаю кнопку. Теперь, как только я добавил 1-й элемент, я хочу удалять первый элемент через каждые 1 секунду, пока массив не станет пустым, и я больше не нажимаю кнопку.

Я мог бы продолжать нажимать и добавлять элементы, но по прошествии 2 секунд, возможно, с помощью таймера, он также должен продолжать удаляться из передней части массива.

 const [id, addId] = useState([])

<button onClick={()=> addId([...id,newId])> Add Id </button> 
 

Итак, у меня есть представление, которое перебирает идентификаторы и рисует их на экране. Теперь, когда он будет нарисован, он должен исчезнуть, а также удаляться из массива после первого нажатия кнопки и удаления их каждые 2 секунды.

Я не могу правильно использовать setInterval, setTimeout и useEffect . Это заканчивается бесконечным циклом…

Допустим, я нажимаю 3 раза, массив будет [1,2,3], а через 1 секунду он должен быть [2,3], затем через 1 секунду он должен быть [3] и, наконец, остановиться с пустым массивом []. Как только я нажимаю кнопку, процесс удаления не должен останавливаться до тех пор, пока массив остается пустым, даже когда я перестаю нажимать на кнопку.

Надеюсь, теперь вам ясно, чего я хочу

Комментарии:

1. что не работает?

2. Я обновил выпуск @NinaScholz

Ответ №1:

Это мой удар по нему, хотя, возможно, он немного более грязный, чем хотелось бы:

(Редактировать: это основано на ссылке codesandbox КСТАТИ.)

 import React from "react";
import "./styles.css";

export default function App() {
  const delay = 2000;
  const [ids, setIds] = React.useState([]);

  const timeout = React.useRef(null);
  const ids_length = React.useRef(null);

  function set_remove_ids_timeout() {
    if(timeout.current)
      clearTimeout(timeout.current);
    
    timeout.current = setTimeout(() => {
      if (ids_length.current === 0) return;
      ids_length.current = ids_length.current - 1;
      setIds(([first, ...others]) => [...others]);
      set_remove_ids_timeout();
    }, delay);
  }

  function add() {
    ids_length.current = ids.length   1;
    setIds([...ids, (ids[ids.length - 1] || 0)   1]);
    set_remove_ids_timeout();
  }

  React.useEffect(() => {
    return () => { if(timeout.current) clearTimeout(timeout.current); };
  }, []);

  console.log("ids adding", ids);
  return (
    <div className="App">
      <button onClick={add}> Add Id </button>
      {ids.map((i) => (
        <p key={i}>{i}</p>
      ))}
    </div>
  );
}

 

Редактировать… так что, возможно, я не понял / не понимаю, когда именно возобновляется удаление после нажатия кнопки… итак, если нажатие кнопки не должно влиять на задержку удаления, возможно, сработает что-то вроде этого:

 import React from "react";
import "./styles.css";

export default function App() {
  const delay = 2000;
  const [ids, setIds] = React.useState([]);

  const interval = React.useRef(null);
  const ids_length = React.useRef(null);

  function set_remove_ids_interval() {
    if (!interval.current) {
      interval.current = setInterval(() => {
        console.log("ids_length.current", ids_length.current);
        if (ids_length.current === 0) {
          clearInterval(interval.current);
          interval.current = null;
          return;
        }
        ids_length.current = ids_length.current - 1;
        setIds(([first, ...others]) => [...others]);
        set_remove_ids_interval();
      }, delay);
    }
  }

  function add() {
    ids_length.current = ids.length   1;
    setIds([...ids, (ids[ids.length - 1] || 0)   1]);
    set_remove_ids_interval();
  }

  React.useEffect(() => {
    return () => {
      if (interval.current) clearInterval(interval.current);
    };
  }, []);

  console.log("ids adding", ids);
  return (
    <div className="App">
      <button onClick={add}> Add Id </button>
      {ids.map((i) => (
        <p key={i}>{i}</p>
      ))}
    </div>
  );
}
 

Комментарии:

1. Привет, Бен Стивен, это работает для того, что мне было нужно, спасибо, но можно ли это сделать без использования ref ?

2. @Rishav Я думаю, вы могли бы попробовать объявить let interval и let ids_length вне области видимости компонента (как в ответе stellyrepsolka, но (я думаю) let вместо const ), но (согласно комментарию Эмиля Бержерона) это ограничивает вас использованием одного экземпляра компонента. Возможно, вы сможете использовать state для interval_id, но мне было бы интересно, если к моменту запуска функции, переданной в setInterval, interval_id внутри этой функции будет устаревшим, поэтому clearInterval(interval_id) не будет работать. Я думаю, что использование state для ids_length не будет работать, так как в интервальной функции оно, вероятно, устарело.

3. @Rishav почему вы не хотите использовать ссылку?

4. Это работает, но я слышал, что использование ref — не лучший способ, и нам следует избегать его использования как можно чаще. Так хотел узнать, но большое спасибо, это работает!

Ответ №2:

Вы могли бы взять setInterval и проверить, есть ли в массиве значения.

 function add() {
    array.push(Math.floor(Math.random() * 10));
    document.getElementById('values').innerHTML = array.join(' ');
}

const array = [];

setInterval(() => {
    if (!array.length) return;
    array.shift();
    document.getElementById('values').innerHTML = array.join(' ');
}, 1000); 
 <button onclick="add()">click</button><br>
<div id="values"></div> 

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

 function showValues() {
    document.getElementById('values').innerHTML = array.join(' ');
}

function add() {
    array.push(Math.floor(Math.random() * 10));
    showValues();
    if (interval === undefined) interval = setInterval(intervalFn, 1000);
}

function intervalFn() {
    array.shift();
    showValues();
    if (!array.length) {
        clearInterval(interval);
        interval = undefined;
    }
}

let array = [],
    interval; 
 <button onclick="add()">click</button><br>
<div id="values"></div> 

Комментарии:

1. Это работает, но мой компонент не выполняет повторный рендеринг, поэтому я не могу видеть, как элементы исчезают. Мне это нужно для работы с React. codesandbox.io/s/busy-fermat-3nrrt?file=/src/App.js . Эффект должен быть похож на числа, которые я показываю внутри тега <p> .

2. извините, я понятия не имею о react .

3. Спасибо за это, действительно ценю это! Это начало. @Nina Scholz

Ответ №3:

 const interval = null;

const Component = () => {

    const [array, setArray] = useState([]);

    useEffect(() =>  

         if (interval === null) {
              interval = setInterval(() => {
                   if (array.length > 0) {
                     const [first, ...newArray] = array;
                     setArray(newArray);
                   }

              }, 1000);
         }

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

   return ( put code that you want to render );
}
 

Инициализируйте интервальную переменную вне компонента, чтобы она не менялась, если в компоненте происходят какие-либо повторные рендеринги. В компоненте mount (use effect) установил interv, который будет срабатывать каждую секунду и удалять элемент из массива (shift или pop) и обновлять состояние. И при размонтировании очистите интервал.

Комментарии:

1. .shift изменяет массив на месте.

2. да, я допустил ошибку. Я это исправил.

3. Кроме того, только что заметил, что interval это определено вне useEffect , что означает, что если когда-либо будет создан другой экземпляр компонента, он сломается.

4. Только что понял, что вы используете array , который фиксируется при монтировании компонента и никогда не получает новых значений, так что это тоже не сработает. Вам нужно будет использовать параметр обратного вызова программы обновления: setArray(([first, ...newArray]) => newArray)