React:使用 Generator 制作一个具有向前和向后方向的循环组件
React: make a cycle-through component with both forward and backward directions using Generator
我有一个字符串数组要显示 const array = ["one", "two", "three"];
。
UI 最初显示数组中的第一项,即 "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
显然当前的行为有问题。有多个问题不知道原因和解决办法:
- UI 以
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
按钮虽然有效。不知道为什么。
getPrev
的问题在于 remainder (%
) operator,它与取模运算 returns 不同,当余数为负时会产生负结果。要解决这个问题,请改用模函数:
// modulo function
const mod = (n, r) => ((n % r) + r) % r;
要解决第一次渲染时的问题,请在组件外部创建初始值。这是一个解决方法,因为我找不到该错误的原因。
const init = getNext(); // get the initial value
export default function App() {
const [current, setCurrent] = useState(init); // use init value
我还需要通过分别在 getNext
和 getPrev
中传递 1
和 -1
来确定增量。
完整代码示例 (sandbox):
// 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>
);
}
问题确实出在余数 (%) 运算符上,改用 const mod = (n, r) => ((n % r) + r) % r;
即可解决。
我想回答我自己的问题,因为我想我明白是什么导致了初始状态的奇怪问题。
- UI以二开头的问题,是React严格模式的双重渲染造成的。
- 然后如果我们使用
const [current, setCurrent] = useState(array[0]);
代替,就会出现另一个问题,您需要双击 right
才能转到 two
。这是因为在生成器中,index
从 0 开始,所以在第一次点击后,迭代器将生成当前的 index
,而不是 0
而不是 1
,因此我们需要双击前进
所以,我建议的答案是:
- 其中之一,正如其中一个答案所提到的,它是由双重渲染引起的。我的建议是不初始化
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;
}
}
使用像 window.log() 这样的 js 日志记录 可以清楚地看到错误是在单击左键后生成的数字是 -2 -1 这不是数组索引所以一个想法可以是让他们 positive.But 第一步是找到错误
我有一个字符串数组要显示 const array = ["one", "two", "three"];
。
UI 最初显示数组中的第一项,即 "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
显然当前的行为有问题。有多个问题不知道原因和解决办法:
- UI 以
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
按钮虽然有效。不知道为什么。
getPrev
的问题在于 remainder (%
) operator,它与取模运算 returns 不同,当余数为负时会产生负结果。要解决这个问题,请改用模函数:
// modulo function
const mod = (n, r) => ((n % r) + r) % r;
要解决第一次渲染时的问题,请在组件外部创建初始值。这是一个解决方法,因为我找不到该错误的原因。
const init = getNext(); // get the initial value
export default function App() {
const [current, setCurrent] = useState(init); // use init value
我还需要通过分别在 getNext
和 getPrev
中传递 1
和 -1
来确定增量。
完整代码示例 (sandbox):
// 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>
);
}
问题确实出在余数 (%) 运算符上,改用 const mod = (n, r) => ((n % r) + r) % r;
即可解决。
我想回答我自己的问题,因为我想我明白是什么导致了初始状态的奇怪问题。
- UI以二开头的问题,是React严格模式的双重渲染造成的。
- 然后如果我们使用
const [current, setCurrent] = useState(array[0]);
代替,就会出现另一个问题,您需要双击right
才能转到two
。这是因为在生成器中,index
从 0 开始,所以在第一次点击后,迭代器将生成当前的index
,而不是0
而不是1
,因此我们需要双击前进
所以,我建议的答案是:
- 其中之一,正如其中一个答案所提到的,它是由双重渲染引起的。我的建议是不初始化
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;
}
}
使用像 window.log() 这样的 js 日志记录