【问题标题】:How do I stop useEffect from making my component re-render infinitely?如何阻止 useEffect 使我的组件无限重新渲染?
【发布时间】:2020-06-26 12:42:09
【问题描述】:

再次编辑以解释=== 比较对象的内存位置(Why are two identical objects not equal to each other?),这就是为什么useEffect() 一次又一次地运行...我想我现在已经对其进行了排序,但将离开在这里发帖暂时没有答案,以防我说的是绝对的胡言乱语,有人想纠正我:)

--- 第二次编辑结束----

我已经编辑了我的Routes const 以将不同的sessions 记录到控制台。如您所见,它们是平等的,但由于某种原因,计算机似乎不这么认为。有谁知道为什么???

edited Routes (to show the values)
const Routes = () => {
  const [session, setSession] = useState(getSessionCookie());
  console.log('Route session before: ', session);


  useEffect(
    () => {
      //setSession(getSessionCookie());
      const stateCookie = session;
      const getCookie = getSessionCookie();
      console.log('stateCookie: ', stateCookie);
      console.log('getCookie: ', getCookie);
      console.log('Are equal? ', stateCookie === getCookie);
    },
    [session]
  );
  console.log('Route session after: ', session);

----- 编辑结束-------

我在网上找到了一个tutorial 来帮助我管理用户会话,但我一直在重新渲染组件时遇到问题,除非我取出 useEffect 依赖项。我已经记录了 session 变量的值和类型,但它没有改变,所以我不明白为什么重新渲染不断发生。任何帮助将不胜感激。

index.js

import React, { useEffect, useState, useContext } from 'react';
import { render } from "react-dom";
import { Router, Switch, Route } from "react-router";
import { Link } from "react-router-dom";
import { createBrowserHistory } from "history";
import Cookies from "js-cookie";
import { SessionContext, getSessionCookie, setSessionCookie } from "./session";

const history = createBrowserHistory();

const LoginHandler = ({ history }) => {
  const [email, setEmail] = useState("");
  const [loading, setLoading] = useState(false);
  const handleSubmit = async e => {
    e.preventDefault();
    setLoading(true);
    // NOTE request to api login here instead of this fake promise
    await new Promise(r => setTimeout(r(), 1000));
    setSessionCookie({ email });
    history.push("/");
    setLoading(false);
  };

  if (loading) {
    return <h4>Logging in...</h4>;
  }

  return (
    <div style={{ marginTop: "1rem" }}>
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          placeholder="Enter email address"
          value={email}
          onChange={e => setEmail(e.target.value)}
        />
        <input type="submit" value="Login" />
      </form>
    </div>
  );
};

const ProtectedHandler = ({ history }) => {
  const session = useContext(SessionContext);
  if (session.email === undefined) {
    history.push("/login");
  }
  return (
    <div>
      <h6>Protected data for {session.email}</h6>
      <Link to="/logout">Logout here</Link>
    </div>
  );
};

const LogoutHandler = ({ history }) => {
  useEffect(
    () => {
      Cookies.remove("session");
      history.push("/login");
    },
    [history]
  );

  return <div>Logging out!</div>;
};

const Routes = () => {
  const [session, setSession] = useState(getSessionCookie());
  console.log('Routes session before: ', session);
  console.log('Routes session before typeof: ', typeof session);
  useEffect(
    () => {
      setSession(getSessionCookie());
    },
    [session] // <-------------- this is the dependency that seems to be causing the trouble
  );
  console.log('Routes session: ', session);
  console.log('Routes session typeof: ', typeof session);

  return (
    <SessionContext.Provider value={session}>
      <Router history={history}>
        <div className="navbar">
          <h6 style={{ display: "inline" }}>Nav Bar</h6>
          <h6 style={{ display: "inline", marginLeft: "5rem" }}>
            {session.email || "No user is logged in"}
          </h6>
        </div>
        <Switch>
          <Route path="/login" component={LoginHandler} />
          <Route path="/logout" component={LogoutHandler} />
          <Route path="*" component={ProtectedHandler} />
        </Switch>
      </Router>
    </SessionContext.Provider>
  );
};

const App = () => (
  <div className="App">
    <Routes />
  </div>
);

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

session.js

import React from "react";
import * as Cookies from "js-cookie";

export const setSessionCookie = (session) => {
  Cookies.remove("session");
  Cookies.set("session", session, { expires: 14 });
};

export const getSessionCookie = () => {
  const sessionCookie = Cookies.get("session");

  if (sessionCookie === undefined) {
    console.log('undefined');
    return {};
  } else {
    return return JSON.parse(sessionCookie);
  }
};

export const SessionContext = React.createContext(getSessionCookie());

【问题讨论】:

  • 你确定是什么原因造成的吗?
  • 不,不是。我所知道的是,如果我删除session 并只保留[],这东西就不会渲染到日落中。
  • 哈哈。我猜会话是一个对象,你怎么知道它没有改变?也许里面有一个正在改变的时间戳?
  • 是的,它是一个对象。我还不能登录所以对象仍然只是{}

标签: reactjs react-hooks use-effect


【解决方案1】:

这是使用useEffect 时非常常见的问题,您不应该在依赖数组中包含对象,因为 Object 只是引用它的引用,而不是实际值。并且当创建会话时,即使您的首选属性值相同,它也会有一个新的引用 => 这就是它创建无限循环的原因。

如果您将session 作为依赖项,您应该明确比较属性值,例如session.value

我没有经常使用上下文 API,但我猜您的 &lt;Routes /&gt; 组件中可能有问题,可能不需要更新 &lt;Routes /&gt; 中的会话,因为它只是充当提供者角色。通常,这是您分配初始上下文值的地方。

一旦用户成功登录,您可以在&lt;LoginHandler /&gt; 中更新会话。其他消耗上下文值的子组件只需使用useContext即可获取最新的会话值。

所以,基本上你的应用可能看起来像这样:

// sessionContext.js
const SessionContext = React.createContext({
  session: {},
  setSession: () => {},
});

// components/routes.js
const Routes = () => {
  const [session, setSession] = useState({})
  const contextSession = {
    session,
    setSession
  }

  return (
    <SessionContext.Provider value={contextSession}>
      {children}
    </SessionContext.Provider>
  )
}

// components/childComponent.js
const ChildComponent = () => {
  const { session } = useContext(SessionContext)

  if (!session)
    return null;
  
  return <div>Logged-in</div>
}

对于复杂的状态管理,我建议看一下redux,你不需要像上面的例子那样使用上下文。

【讨论】:

  • 非常感谢@Duc Hong。我会尝试你的建议,看看我是否可以让它工作。如果是这样,我会将您的回复标记为答案。
  • 这里主要是了解上下文 API 及其用法,您的逻辑依赖于&lt;Routes /&gt;,同时在useEffect 中不断更新它,实际上子组件不需要通过道具接收其会话值, 可以通过useContext直接获取这个数据
  • 谢谢 - 显然我需要研究 useContextHook 的其余功能以了解该怎么做!
【解决方案2】:

鉴于您应该只在会话更新/更改时记录会话,因此您应该在更新状态之前进行比较,否则会在您不断更新状态时导致无限循环。

useEffect(() => {
  // assuming that session is not an array or object
  if (getSessionCookie().email === session.email) {
    return;
  }
  setSession(getSessionCookie());
}, [session]);

同样,在您的 LogoutHandler 组件上,您不应该在将 history 对象作为依赖数组的一部分时更新它。事实上,没有必要调用history.push(),因为在渲染组件时您应该已经在该路由中。您应该只删除一次 cookie,因此您可以在安装组件时调用它。

const LogoutHandler = ({ history }) => {
  useEffect(() => {
    Cookies.remove("session");
  }, []);

  return <div>Logging out!</div>;
};

【讨论】:

  • 我现在要试试你的建议。让我感到困惑的是沙盒(codesandbox.io/s/389o3qyoq5?file=/src/index.tsx)按原样工作......
  • 我正要告诉你它是一个对象 :) 它目前是一个空对象,因为我还不能使用代码登录。
  • 是的。在这种情况下,您需要在属性之间进行比较。 email 是会话对象中的唯一字段吗?我已经更新了我的答案
  • 它适用于登录部分,但注销后出现Maximum update depth exceeded 错误
  • 你试过我在 LogoutHandler 上实现的吗?我使用了一个空数组([])作为 useEffect 钩子的依赖数组
猜你喜欢
  • 2022-01-12
  • 1970-01-01
  • 2020-07-06
  • 2021-04-27
  • 2020-06-28
  • 1970-01-01
  • 2021-11-26
  • 2021-09-14
  • 2020-01-20
相关资源
最近更新 更多