please explain the error to me, Error: Rendered more hooks than during the previous render

please explain the error to me, Error: Rendered more hooks than during the previous render

我是新手,现在正在学习制作自定义 React 挂钩 在这里,我试图调用我在 app.js 文件中创建的函数,我想在单击按钮时使用它。但没有这样做。请帮我找出错误并理解它。

import React, {
  useEffect,
  useState
} from "react";

const useRandomJoke = () => {
  const [jokes, setJokes] = useState();
  useEffect(() => {
    const jokeFetch = async() => {
      await fetch("https://api.icndb.com/jokes/random")
        //we'll run 2 "then"
        .then(
          // this will give us response and will return inform of res.json
          (res) => res.json()
        ) //.json is a format
        .then((data) => {
          setJokes(data.value.joke);
        }); // now calling data from te returned values in res.json
    };
    jokeFetch();
  }, []);
  return jokes;
};

export default useRandomJoke;
//With onClick function 

function App() { const [jokes, setJokes] = useState(); 
return (
<div className="App">
  <h1>Random Jokes</h1>
  <p>{jokes}</p>
  <button onClick={()=>{setJokes(useRandomJoke)}}>
     Click for Jokes</button>
</div>
); } export default App;

`

useRandomJoke 是自定义挂钩。挂钩只能在组件的顶层调用,并且由于自定义挂钩已经具有 joke 状态,因此您不需要在 App 组件中添加其他状态。

如果您想在组件呈现后以及每次单击按钮时获得一个新笑话,您可以这样做:

const useRandomJoke = () => {
  const [joke, setJoke] = useState("");

  const fetchJoke = useCallback(() => {
    fetch("https://api.icndb.com/jokes/random")
      .then((res) => res.json())
      .then((data) => {
        setJoke(data.value.joke);
      });
  }, []);

  return [joke, fetchJoke];
};

export default function App() {
  const [joke, fetchJoke] = useRandomJoke();

  useEffect(() => {
    fetchJoke();
  }, [fetchJoke]);

  return (
    <div className="App">
      <h1>Random Jokes</h1>
      <p>{joke}</p>
      <button onClick={fetchJoke}>Click for a random joke</button>
    </div>
  );
}

您不能有条件地调用 React 挂钩,就像在按钮的 onClick 处理程序中一样,因为这会破坏 rules of hooks。我建议将 useRandomJoke 钩子重构为 return 获取的笑话 获取下一个随机笑话的函数。您也不应该将 async/await 与 Promise 链混合使用,因为这是一种反模式。

const useRandomJoke = () => {
  const [jokes, setJokes] = useState(null);

  const jokeFetch = async () => {
    const res = await fetch("https://api.icndb.com/jokes/random");
    const data = await res.json();
    setJokes(data.value.joke)
  };

  return [jokes, jokeFetch];
};

然后在应用程序中使用挂钩。

function App() {
  const [joke, getJoke] = useRandomJoke();

  return (
    <div className="App">
      <h1>Random Jokes</h1>
      <p>{joke}</p>
      <button onClick={getJoke}>Click for Joke</button>
    </div>
  );
}

嗯,这里要讲的不止一点:

1-在React.js中,你只能在函数体的顶层调用自定义钩子(react 将任何以关键字 use 开头的函数识别为钩子)

function App() { 
  // top level is here
  
  const randomJokes = useRandomJoke()

  const [jokes, setJokes] = useState(); 
  return (
    <div className="App">
      <h1>Random Jokes</h1>
      <p>{jokes}</p>
      <button onClick={()=>{setJokes(useRandomJoke)}}>
        Click for Jokes
       </button>
    </div>
); } 
export default App;

2- 在您的示例中,我知道您想在每次 onClick 触发时都有一个新笑话,为了做到这一点,我认为使用自定义挂钩不是理想的解决方案,因为您的自定义挂钩仅在初始渲染时运行 fetchJokes 方法一次(如您在 useEffect 挂钩中所述),我知道很多人提到 useEffect 是制作 API 调用,但它不一定适用于所有用例,在您的示例中很简单,您不必使用 useEffect 也不必创建自定义挂钩。

一个可能的简单解决方案:

function App() {
  // we always call hooks at the top level of our function
  const [jokes, setJokes] = useState();
  const fetchNewJoke = () => {
  
      fetch("https://api.icndb.com/jokes/random")
        //we'll run 2 "then"
        .then(
          // this will give us response and will return inform of 
           res.json
          (res) => res.json()
        ) //.json is a format
        .then((data) => {
          setJokes(data.value.joke);
        }); // now calling data from te returned values in res.json
    };
};

    return (
     <div className="App">
      <h1>Random Jokes</h1>
      <p>{jokes}</p>
      <button onClick={fetchNewJoke}>Click for Joke</button>
     </div>
    );
} export default App;