使用效果钩中的问题

发布于 2025-02-04 07:14:39 字数 2865 浏览 3 评论 0原文

假设我有一个称为 count 的状态。

const [count, setCount] = useState();

现在,假设我想增加 count ,每次按下键盘中的某些键时。
因此,我可以使用使用效果挂钩并添加事件侦听器。

 useEffect(() => {
    document.addEventListener("keydown", increaseCount);
 }, []);

功能增加了,增加了计数,其中1

 const increaseCount = () => {
     setCount(prevCount => prevCount + 1);
};

井都奏效了。 ,但是,如果我想在增加估计中获得 count 的当前值,我将无法做到!事件侦听器仅在组件安装时一次调用一次(因为使用效果具有空的依赖关系数组)。

如果我将 count 添加到依赖项数组中,我有一个新问题 - 我正在创建一种循环,因为使用效率会调用fightecount,它将调用setCount(),这将导致组件重新渲染,这将再次调用使用效果,依此类推。

我在目前正在从事的一些项目上遇到了这种问题,这非常令人沮丧。因此,如果您知道如何回答 - 谢谢! :)

摘要

使用空的依赖项数组和登录计数在增加量, count 将始终为0:

// Get a hook function
    const {useState, useEffect} = React;

    const Counter = () => {
      const [count, setCount] = useState(0);

  useEffect(() => {
    document.addEventListener("keydown", increaseCount);
  }, []);

  const increaseCount = () => {
    setCount((prevCount) => prevCount + 1);
    console.log(count);
  };

      return (
        <div>
          count = {count}
        </div>
      );
    };

    // Render it
    ReactDOM.render(
      <Counter />,
      document.getElementById("react")
    );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
    <div id="react"></div>

当将 count 添加到依赖项数组中时,我们会看到这种“循环”发生的事情:

// Get a hook function
    const {useState, useEffect} = React;

    const Counter = () => {
      const [count, setCount] = useState(0);

  useEffect(() => {
    document.addEventListener("keydown", increaseCount);
  }, [count]);

  const increaseCount = () => {
    setCount((prevCount) => prevCount + 1);
    console.log(count);
  };

      return (
        <div>
          count = {count}
        </div>
      );
    };

    // Render it
    ReactDOM.render(
      <Counter />,
      document.getElementById("react")
    );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
    <div id="react"></div>

Let's say I have a state called count.

const [count, setCount] = useState();

Now let's say I would like to increase count with 1 every time some key in the keyboard is being pressed.
So I can use useEffect hook and add an event listener to it.

 useEffect(() => {
    document.addEventListener("keydown", increaseCount);
 }, []);

The function increaseCount, increasing count with 1

 const increaseCount = () => {
     setCount(prevCount => prevCount + 1);
};

Well everything is working great. BUT, if I want to get the current value of count inside increaseCount, I can't do this! The event listener is only called once when the component is mounting (because the useEffect has an empty dependency array).

And if I add count to the dependency array, I have a new problem - I'm creating a kind of loop, because useEffect will call increaseCount, that will call setCount(), that will cause the component to re-render, which will call useEffect again and so on and so on.

I have this kind of problem on a few projects I'm currently working on, and it is very frustrating. So if you know how to answer this - thanks! :)

snippets

When using an empty dependency array and login count inside increaseCount, count will always be 0:

// Get a hook function
    const {useState, useEffect} = React;

    const Counter = () => {
      const [count, setCount] = useState(0);

  useEffect(() => {
    document.addEventListener("keydown", increaseCount);
  }, []);

  const increaseCount = () => {
    setCount((prevCount) => prevCount + 1);
    console.log(count);
  };

      return (
        <div>
          count = {count}
        </div>
      );
    };

    // Render it
    ReactDOM.render(
      <Counter />,
      document.getElementById("react")
    );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
    <div id="react"></div>

and when adding count to the dependency array, we see this "loop" thing happening:

// Get a hook function
    const {useState, useEffect} = React;

    const Counter = () => {
      const [count, setCount] = useState(0);

  useEffect(() => {
    document.addEventListener("keydown", increaseCount);
  }, [count]);

  const increaseCount = () => {
    setCount((prevCount) => prevCount + 1);
    console.log(count);
  };

      return (
        <div>
          count = {count}
        </div>
      );
    };

    // Render it
    ReactDOM.render(
      <Counter />,
      document.getElementById("react")
    );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
    <div id="react"></div>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

独守阴晴ぅ圆缺 2025-02-11 07:14:39

您的应该做的事情确实取决于您需要做什么。参见 xy问题

如前所述,您正在尝试多种用例。

const { useState, useEffect, useMemo, Fragment } = React;

const App = () => {
  const [count, setCount] = useState(0);
  function increaseCount(ev) {
    setCount((count) => {
      const newCount = count + 1;
      // You can use the newCount here for something basic if needed...
      console.log(newCount);
      // I'm not positive, but I'm farily sure that setting other state
      // from within a set state function might be problematic.
      // If you feel the need to do something with side effects here,
      // Consider another useEffect as below.
      return newCount;
    });
  }
  useEffect(() => {
    document.addEventListener("keydown", increaseCount);
    // Always clean up after your effect!
    return () => document.removeEventListener("keydown", increaseCount);
    // If you use `increaseCount` here as a dependency (which you should), the function will get recreated every time.
    // Or you could set it up by creating the `increase count function inside the effect itself
  }, [increaseCount]);

  useEffect(() => {
    // If you need to use newCount for something more complicated, do it here...
   // Or for side effects
  }, [count]);

  const arrayCountSized = useMemo(() => {
    // You can use the count here in a useMemo for things that are
    // derived, but not stateful in-of-themselves
    return new Array(count).fill(null).map((_, idx) => idx);
  }, [count]);

  return (
    <Fragment>
      <div>{count}</div>
      <ul>
        {arrayCountSized.map((row) => (
          <li key={row}>{row}</li>
        ))}
      </ul>
    </Fragment>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>


<div id="root"/>


至于您特别提到的情况,这里有一个解决方案。

您可以正常从数组长度获得“当前索引”,这就是我在这里做的。

如果“ currentIndex”比那更复杂,并且与数组状态不可分割地捆绑在一起,则应用户educer以纯粹的方式设置捆绑状态。

您也可以使用对侦听器函数中数组的引用。

const arrayRef = useRef(array);
useEffect(()=>{arrayRef.current=array},[array]);
// arrayRef.current is safe to use in any effect after this.

const { useState, useEffect, useMemo, Fragment } = React;

const App = () => {
  const [array, setArray] = useState([]);
  const curIndex = array.length - 1;
  useEffect(() => {
    function addKey(ev) {
      if (ev.key.length === 1 && ev.key >= "a" && ev.key <= "z") {
        setArray((arr) => [...arr, ev.key]);
      }
    }
    document.addEventListener("keydown", addKey);
    // Always clean up after your effect!
    return () => document.removeEventListener("keydown", addKey);
  }, []);

  return (
    <Fragment>
      <div>Current Index: {curIndex}</div>
      <div>Keys pressed: </div>
      <ul>
        {array.map((row, index) => (
          <li key={index}>{row}</li>
        ))}
      </ul>
    </Fragment>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>


<div id="root"/>


用户使用方法:

由于我没有足够的详细信息,因此在还原器中,这与上述解决方案相同。

const { useState, useEffect, useReducer, useMemo, Fragment } = React;

function reducer(state, key) {
  if (!key) {
    return state;
  }
  const array = [...state.array, key];
  return { array, currentIndex: array.length - 1 };
}

const App = () => {
  const [{ array, currentIndex }, addKey] = useReducer(reducer, {
    array: [],
    currentIndex: -1,
  });
  useEffect(() => {
    function keydownListener(ev) {
      if (ev.key.length === 1 && ev.key >= "a" && ev.key <= "z") {
        addKey(ev.key);
      }
    }
    document.addEventListener("keydown", keydownListener);
    // Always clean up after your effect!
    return () => document.removeEventListener("keydown", keydownListener);
  }, []);

  return (
    <Fragment>
      <div>Current Index: {currentIndex}</div>
      <div>Keys pressed: </div>
      <ul>
        {array.map((row, index) => (
          <li key={index}>{row}</li>
        ))}
      </ul>
    </Fragment>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>


<div id="root"/>

What you should do really depends on what you need to do. See X Y problem.

As mentioned there are multiple use cases you are trying for.

const { useState, useEffect, useMemo, Fragment } = React;

const App = () => {
  const [count, setCount] = useState(0);
  function increaseCount(ev) {
    setCount((count) => {
      const newCount = count + 1;
      // You can use the newCount here for something basic if needed...
      console.log(newCount);
      // I'm not positive, but I'm farily sure that setting other state
      // from within a set state function might be problematic.
      // If you feel the need to do something with side effects here,
      // Consider another useEffect as below.
      return newCount;
    });
  }
  useEffect(() => {
    document.addEventListener("keydown", increaseCount);
    // Always clean up after your effect!
    return () => document.removeEventListener("keydown", increaseCount);
    // If you use `increaseCount` here as a dependency (which you should), the function will get recreated every time.
    // Or you could set it up by creating the `increase count function inside the effect itself
  }, [increaseCount]);

  useEffect(() => {
    // If you need to use newCount for something more complicated, do it here...
   // Or for side effects
  }, [count]);

  const arrayCountSized = useMemo(() => {
    // You can use the count here in a useMemo for things that are
    // derived, but not stateful in-of-themselves
    return new Array(count).fill(null).map((_, idx) => idx);
  }, [count]);

  return (
    <Fragment>
      <div>{count}</div>
      <ul>
        {arrayCountSized.map((row) => (
          <li key={row}>{row}</li>
        ))}
      </ul>
    </Fragment>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>


<div id="root"/>


As for the situation you mentioned in particular, here's one solution.

You can get the "current Index" from the array length normally, which is what I'm doing here.

If the "currentIndex" is more complicated than that and inexorably tied together with the array state, you should useReducer to set up that tied state in a pure fashion.

You could also use a reference to the array in your listener function.

const arrayRef = useRef(array);
useEffect(()=>{arrayRef.current=array},[array]);
// arrayRef.current is safe to use in any effect after this.

const { useState, useEffect, useMemo, Fragment } = React;

const App = () => {
  const [array, setArray] = useState([]);
  const curIndex = array.length - 1;
  useEffect(() => {
    function addKey(ev) {
      if (ev.key.length === 1 && ev.key >= "a" && ev.key <= "z") {
        setArray((arr) => [...arr, ev.key]);
      }
    }
    document.addEventListener("keydown", addKey);
    // Always clean up after your effect!
    return () => document.removeEventListener("keydown", addKey);
  }, []);

  return (
    <Fragment>
      <div>Current Index: {curIndex}</div>
      <div>Keys pressed: </div>
      <ul>
        {array.map((row, index) => (
          <li key={index}>{row}</li>
        ))}
      </ul>
    </Fragment>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>


<div id="root"/>


useReducer Approach:

As I don't have enough details, this is the same solution as above, just in a reducer.

const { useState, useEffect, useReducer, useMemo, Fragment } = React;

function reducer(state, key) {
  if (!key) {
    return state;
  }
  const array = [...state.array, key];
  return { array, currentIndex: array.length - 1 };
}

const App = () => {
  const [{ array, currentIndex }, addKey] = useReducer(reducer, {
    array: [],
    currentIndex: -1,
  });
  useEffect(() => {
    function keydownListener(ev) {
      if (ev.key.length === 1 && ev.key >= "a" && ev.key <= "z") {
        addKey(ev.key);
      }
    }
    document.addEventListener("keydown", keydownListener);
    // Always clean up after your effect!
    return () => document.removeEventListener("keydown", keydownListener);
  }, []);

  return (
    <Fragment>
      <div>Current Index: {currentIndex}</div>
      <div>Keys pressed: </div>
      <ul>
        {array.map((row, index) => (
          <li key={index}>{row}</li>
        ))}
      </ul>
    </Fragment>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>


<div id="root"/>

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文