如何防止 useEffect() 运行 在 运行 在上下文消费者中调用函数后两次并防止 useContext() 重新渲染

How to prevent useEffect() run twice after running a function in context consumer and prevent useContext() to re-render

我了解到 React 会在状态改变后重新渲染,例如setState 来自 useState(),调用来自 useContext() 变量的函数或变量。但现在我不明白为什么我会收到 ESLint 警告,在列表中没有依赖项的情况下调用 useCallback() 中的上下文函数。如果我将依赖项放入列表中,useCallback() 将被重新渲染,并且来自 useCallback() 变量的 useEffect() 依赖项将再次执行。那么如何在 useContext() 变量中调用函数时修复 react-hooks/exhaustive-deps 呢?

Auth.js

import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
import * as AuthAPI from "../API/AuthAPI"
import Loading from "../Page/Loading"

const AuthContext = createContext()

export const AuthProvider = ({children}) => {

  const [user,setUser] = useState()
  const [loadingInitial,setLoadingInitial] = useState(true)

  useEffect(()=>{
    AuthAPI.getCurrentUser()
    .then((user)=>setUser(user))
    .catch((error)=>{console.log(error)})
    .finally(()=>setLoadingInitial(false))
  },[])

  const login = async (email,password) => {
    const user = await AuthAPI.login({email,password})
    setUser(user)
    return user
  }

  const register = async (firstname,lastname,email,password) => {
    const user = await AuthAPI.register({firstname,lastname,email,password})
    setUser(user)
    return user
  }

  const logout = async () => {
    const response = await AuthAPI.logout()
    setUser(undefined)
  }

  const value = useMemo(()=>({
      user,
      setUser,
      login,
      register,
      logout
  }),[user])

  return (
    <AuthContext.Provider value={value}>
      {loadingInitial ? <Loading/> : children}
    </AuthContext.Provider>
  )
}

export const useAuth = () => {
  return useContext(AuthContext)
}

Logout.js

import { useCallback, useEffect, useState } from "react";
import { Navigate, useLocation, useNavigate } from "react-router-dom";
import { useAuth } from "../Hooks/Auth";
import * as AuthAPI from "../API/AuthAPI"
import Loading from "./Loading";

function Logout() {
  const auth = useAuth()
  const location = useLocation()
  const navigate = useNavigate()

  const [isLoggedOut,setIsLoggedOut] = useState(false)

  const logout = useCallback(async () => {
    console.log("Logging out!")
    await AuthAPI.logout()
    auth.setUser((prevState)=>(undefined))
    setIsLoggedOut(true)
  },[auth]) // --> re-rendered bacause `auth` context in re-rendered when set `user` state.

  useEffect(()=>{
    logout()
  },[logout]) // --> this also to run again from `logout` callback is being re-rendered.

  if (!isLoggedOut) {
    return <Loading/>
  }

  return (
    <Navigate to="/login" replace/>
  )
}

export default Logout

感谢任何帮助。

您能否尝试将您的 useEffect 代码替换为: 使用效果(注销,[])

既然你只在 useEffect 中使用 setUser,那么解构你的 auth 上下文怎么样?

const { setUser } = useAuth()

useEffect(() => {

....
}, [setUser])

如果 logout 不是 used/passed 回调函数,则无需创建记忆 logout 回调函数。只需在 useEffect 挂钩中应用注销逻辑。

呈现 Loading 组件并从 return AuthAPI.logout Promise 的已解析 Promise 链发出命令式重定向。

示例:

import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../Hooks/Auth";
import * as AuthAPI from "../API/AuthAPI"
import Loading from "./Loading";

function Logout() {
  const auth = useAuth();
  const navigate = useNavigate();
    
  useEffect(() => {
    console.log("Logging out!");
    AuthAPI.logout()
      .then(() => auth.setUser(undefined))
      .finally(() => navigate("/login", { replace: true }));
  }, []);
    
  return <Loading />;
}
    
export default Logout;