React Hook "useEffect" 被有条件地调用

IT技术 javascript reactjs react-hooks
2021-04-28 02:08:25

React 抱怨下面的代码,说它 useEffect 被有条件地调用:

import React, { useEffect, useState } from 'react'
import VerifiedUserOutlined from '@material-ui/icons/VerifiedUserOutlined'
import withStyles from '@material-ui/core/styles/withStyles'
import firebase from '../firebase'
import { withRouter } from 'react-router-dom'

function Dashboard(props) {
  const { classes } = props
  
  const [quote, setQuote] = useState('')

    if(!firebase.getCurrentUsername()) {
        // not logged in
        alert('Please login first')
        props.history.replace('/login')
        return null
    }

    useEffect(() => {
        firebase.getCurrentUserQuote().then(setQuote)
    })

    return (
        <main>
            // some code here
        </main>
    )

    async function logout() {
        await firebase.logout()
        props.history.push('/')
    }
}

export default withRouter(withStyles(styles)(Dashboard))

这让我返回错误:

React Hook "useEffect" 被有条件地调用。在每个组件渲染中,必须以完全相同的顺序调用 React Hook。

有谁碰巧知道这里的问题是什么?

4个回答

您的代码在if包含语句之后return相当于一个else分支:

if(!firebase.getCurrentUsername()) {
    ...
    return null
} else {
    useEffect(...)
    ...
}

这意味着它是有条件地执行的(仅当未return执行时)。

修理:

useEffect(() => {
  if(firebase.getCurrentUsername()) {
    firebase.getCurrentUserQuote().then(setQuote)
  }
}, [firebase.getCurrentUsername(), firebase.getCurrentUserQuote()])

if(!firebase.getCurrentUsername()) {
  ...
  return null
}

不要在循环、条件或嵌套函数中调用 Hook。相反,始终在 React 函数的顶层使用 Hook。您可以按照此处的文档进行操作

我在上面的代码中找不到用例。如果您需要在返回值firebase.getCurrentUsername()更改时运行效果,您可能希望在if条件之外使用它,例如:

useEffect(() => {
    firebase.getCurrentUserQuote().then(setQuote)
}, [firebase.getCurrentUsername()]);

这里的问题是,当我们null返回if blockuseEffect钩子代码将无法访问,因为我们在它之前返回,因此有条件地调用它的错误。

您可能希望首先定义所有钩子,然后开始编写渲染逻辑,无论是 null 或空字符串,还是有效的 JSX。

我认为有一种方法可以有条件地调用钩子。你只需要从那个钩子中导出一些成员。将此代码段复制粘贴到 codeandbox 中:

import React from "react";
import ReactDOM from "react-dom";

function useFetch() {
  return {
    todos: () =>
      fetch("https://jsonplaceholder.typicode.com/todos/1").then(response =>
        response.json()
      )
  };
}

const App = () => {
  const fetch = useFetch(); // get a reference to the hook

  if ("called conditionally") {
    fetch.todos().then(({title}) => 
      console.log("it works: ", title)); // it works:  delectus aut autem  
  }

  return null;
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

这是一个带有包装的示例useEffect

import React, { useEffect } from "react";
import ReactDOM from "react-dom";

function useWrappedEffect() {
  const [runEffect, setRunEffect] = React.useState(false);

  useEffect(() => {
    if (runEffect) {
      console.log("running");
      setRunEffect(false);
    }
  }, [runEffect]);

  return {
    run: () => {
      setRunEffect(true);
    }
  };
}

const App = () => {
  const myEffect = useWrappedEffect(); // get a reference to the hook
  const [run, setRun] = React.useState(false);

  if (run) {
    myEffect.run();
    setRun(false);
  }

  return (
    <button
      onClick={() => {
        setRun(true);
      }}
    >
      Run
    </button>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);