#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
Очевидно, текущее поведение является ошибочным. Существует множество проблем, причин и решений которых я не знаю:
- пользовательский интерфейс начинается с
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
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;
вместо этого исправило бы ее.
Я хотел ответить на свои собственные вопросы, потому что, думаю, я понимаю, что вызвало странные проблемы с исходным состоянием.
- Что касается проблемы с пользовательским интерфейсом, начинающимся с двух, а не с одного, то она была вызвана двойным рендерингом строгого режима React.
- И тогда, если мы будем использовать
const [current, setCurrent] = useState(array[0]);
вместо этого, возникнет еще одна проблема, когда вам нужно будет дважды щелкнутьright
, чтобы перейтиtwo
. Это связано с тем, что в генератореindex
начинается с 0, поэтому после первого щелчка итератор выдаст токindex
, которого0
нет1
, поэтому нам нужно дважды щелкнуть по нему, чтобы двигаться вперед.
Ответ №3:
Итак, мой предлагаемый ответ таков:
- Во-первых, как уже упоминалось в одном из ответов, это вызвано двойным рендерингом. Мое предложение состоит в том, чтобы инициализировать значение не в
useState
, а вuseEffect
крючке с пустыми зависимостями. Это приведет к тому, что он будет запущен только один раз при первом рендеринге. Пример:
useEffect(() => {
setCurrent(() => getNext())
}, [])
- Ошибка вызвана тем, что вы вычитаете 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