Реагируйте: создайте циклический компонент как в прямом, так и в обратном направлениях с помощью генератора

#javascript #reactjs #ecmascript-6 #es6-generator

Вопрос:

У меня есть массив строк для отображения const array = ["one", "two", "three"]; .

Пользовательский интерфейс изначально отображает первый элемент в массиве, т. е. "one" . Оттуда у меня есть кнопка right , при нажатии на которую отображается следующий элемент или строка , которая есть two , а затем three , после three того, как она должна вернуться one и начать оттуда снова. У меня также есть left кнопка, при нажатии на которую отображается предыдущий элемент или строка, если текущая строка two , то предыдущая строка one , а затем после one того, как она начинается three и идет назад.

Для этого я использую генератор. Вот моя попытка

 
function* stepGen(steps) {
  let index = 0;
  while (true) {
    const direction = yield steps[index];
    index = (index   (direction === "forward" ? 1 : -1)) % steps.length;
  }
}

const array = ["one", "two", "three"];
let gen = stepGen(array);
const getNext = () => gen.next("forward").value;
const getPrev = () => gen.next("backward").value;

export default function App() {
  const [current, setCurrent] = useState(() => getNext());
  const onRight = () => {
    const next = getNext();
    setCurrent(next);
  };
  const onLeft = () => {
    const prev = getPrev();
    setCurrent(prev);
  };

  return (
    <div className="App">
      <h1>{current}</h1>
      <button onClick={onLeft}>left</button>
      <button onClick={onRight}>right</button>
    </div>
  );
}


 

Вот живая демо-версия, с которой вы можете играть
https://codesandbox.io/s/cyclethrough1-deh8p?file=/src/App.js

Очевидно, текущее поведение является ошибочным. Существует множество проблем, причин и решений которых я не знаю:

  1. пользовательский интерфейс начинается с two «нет one «. Я думаю, это как-то связано с тем, как я инициирую свое состояние current
 const [current, setCurrent] = useState(() => getNext());
 

Я думал () => getNext() , что только один раз вызывается, когда компонент впервые монтируется, так current должно быть one с самого начала.

И я попытался инициировать государство с помощью

 const [current, setCurrent] = useState(array[0]);
 

Он действительно начинается с первого элемента в массиве, который есть one , но вам нужно right дважды нажать кнопку, чтобы перейти к two нему . Вот живая демонстрация этой вариации https://codesandbox.io/s/cyclethrough2-5gews?file=/src/App.js

  1. left кнопка, которая должна идти назад по циклу, не работает. Он полностью сломан. однако right кнопка работает. Не знаю почему.

Ответ №1:

Проблема getPrev заключается в операторе остатка ( % ), который, в отличие от операции по модулю, возвращает отрицательный результат, когда остаток отрицателен. Чтобы решить эту проблему, вместо этого используйте функцию по модулю:

 // modulo function
const mod = (n, r) => ((n % r)   r) % r;
 

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

 const init = getNext(); // get the initial value

export default function App() {
  const [current, setCurrent] = useState(init); // use init value
 

Я бы также избавил от необходимости в троичной системе для определения приращения путем передачи 1 и -1 в getNext и getPrev соответственно.

Полный пример кода (песочница):

 // modulo function
const mod = (n, r) => ((n % r)   r) % r;

function* stepGen(steps) {
  let index = 0;
  while (true) {
    const dir = yield steps[index];
    index = mod(index   dir, steps.length); // use mod function instead of remainder operator
  }
}

const array = ['one', 'two', 'three'];
const gen = stepGen(array);

const getPrev = () => gen.next(-1).value; // dec directly
const getNext = () => gen.next(1).value; // inc directly

const init = getNext(); // get the initial value

export default function App() {
  const [current, setCurrent] = useState(init); // use init value

  const onLeft = () => {
    const next = getPrev();
    setCurrent(next);
  };

  const onRight = () => {
    const prev = getNext();
    setCurrent(prev);
  };

  return (
    <div className="App">
      <h1>{current}</h1>
      <button onClick={onLeft}>left</button>
      <button onClick={onRight}>right</button>
    </div>
  );
}
 

Ответ №2:

Проблема действительно была с оператором остатка ( % ), и использование const mod = (n, r) => ((n % r) r) % r; вместо этого исправило бы ее.

Я хотел ответить на свои собственные вопросы, потому что, думаю, я понимаю, что вызвало странные проблемы с исходным состоянием.

  1. Что касается проблемы с пользовательским интерфейсом, начинающимся с двух, а не с одного, то она была вызвана двойным рендерингом строгого режима React.
  2. И тогда, если мы будем использовать const [current, setCurrent] = useState(array[0]); вместо этого, возникнет еще одна проблема, когда вам нужно будет дважды щелкнуть right , чтобы перейти two . Это связано с тем, что в генераторе index начинается с 0, поэтому после первого щелчка итератор выдаст ток index , которого 0 нет 1 , поэтому нам нужно дважды щелкнуть по нему, чтобы двигаться вперед.

Ответ №3:

Итак, мой предлагаемый ответ таков:

  1. Во-первых, как уже упоминалось в одном из ответов, это вызвано двойным рендерингом. Мое предложение состоит в том, чтобы инициализировать значение не в useState , а в useEffect крючке с пустыми зависимостями. Это приведет к тому, что он будет запущен только один раз при первом рендеринге. Пример:
   useEffect(() => {
    setCurrent(() => getNext())
  }, [])

 
  1. Ошибка вызвана тем, что вы вычитаете 1 из индекса. Когда он находится в позиции 0, это приводит к индексу -1, который не дает элемента. Простое решение состоит в том, чтобы зациклить индекс до последнего элемента, если он отрицательный.
 function* stepGen(steps) {
  let index = 0;
  while (true) {
    const direction = yield steps[index];
    index = (index   (direction === "forward" ? 1 : -1)) % steps.length;
    if (index < 0) {
      index = steps.length - 1;
    }
  }

 

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

1. эй, спасибо за ответ. Интересно, значит, обратный вызов в setState будет вызываться дважды в строгом режиме, но обратный вызов в useEffect не будет? я этого не знал.

2. @Joji Точно, useEffect вызывается при обновлении элементов в массиве зависимостей. Если вы передадите пустой массив в качестве зависимости, он будет выполняться только при начальном рендеринге. Я чувствую, что это довольно исчерпывающее объяснение этого dmitripavlutin.com/react-useeffect-explanation

Ответ №4:

Используйте протоколирование js, как window.log ()введите описание изображения здесь, Ясно видно, что ошибка после нажатия влево генерируется число -2 -1, которые не являются индексами массива, поэтому одна идея может заключаться в том, чтобы сделать их положительными.Но первый шаг-найти ошибку