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

显然当前的行为有问题。有多个问题不知道原因和解决办法:

  1. 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

  1. 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

我还需要通过分别在 getNextgetPrev 中传递 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; 即可解决。

我想回答我自己的问题,因为我想我明白是什么导致了初始状态的奇怪问题。

  1. UI以二开头的问题,是React严格模式的双重渲染造成的。
  2. 然后如果我们使用 const [current, setCurrent] = useState(array[0]); 代替,就会出现另一个问题,您需要双击 right 才能转到 two。这是因为在生成器中,index 从 0 开始,所以在第一次点击后,迭代器将生成当前的 index,而不是 0 而不是 1,因此我们需要双击前进

所以,我建议的答案是:

  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;
    }
  }

使用像 window.log() 这样的 js 日志记录 可以清楚地看到错误是在单击左键后生成的数字是 -2 -1 这不是数组索引所以一个想法可以是让他们 positive.But 第一步是找到错误