React - 即使状态变量没有变化,useEffect 也会运行

Posted

技术标签:

【中文标题】React - 即使状态变量没有变化,useEffect 也会运行【英文标题】:React - useEffect running even when there was no change in state variable 【发布时间】:2019-10-28 13:36:06 【问题描述】:

我的 kotlin 应用程序中有一个端点,如下所示:

    either.eager<String, Unit> 
      val sessionAndCookieUser = commonAuth.decryptCookieGetUser(getCookie(context), ::userTransform).bind()
      val user = sessionAndCookieUser.session.user
      val ctx = Ctx(ds, SystemSession, conf)
      val dbUser = getUserEither(ctx, user.id).bind()

      val signatureAlgorithm = SignatureAlgorithm.HS256
      val signingKey = SecretKeySpec(conf.get(ZendeskJWTSecret).toByteArray(), signatureAlgorithm.jcaName)

      val iat = Date(System.currentTimeMillis())
      val exp = Date(System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000)

      val token = Jwts.builder()
          .claim("name", dbUser.name)
          .claim("email", dbUser.email)
          .setIssuer(conf.get(StreamAppName))
          .setIssuedAt(iat)
          .setExpiration(exp)
          .signWith(signingKey, signatureAlgorithm)
          .compact()

      context.setResponseCode(StatusCode.OK)
          .setResponseType("application/json")
          .send(jsonObject("token" to token).toString())
    .mapLeft 
      context.setResponseCode(StatusCode.UNAUTHORIZED)
    

我正在设置一个响应,如果用户通过身份验证,我应该发送jsonObject,如果用户未通过身份验证,我应该发送UNAUTHORIZED。 当我在浏览器中测试这个端点时,我只会得到 status unknown 的那个请求——当我调试后端时,否则我得到 200 没有响应数据。 如果我在邮递员中测试它,我会得到 json 作为响应。 我看到正在构建令牌,并且后端的一切看起来都很好,但是没有在浏览器中加载响应。

我是这样从 react 中获取的:

export const fetchGet = (uriPath: string) => 
  fetch(fullUrl(uriPath), 
    method: 'GET',
    credentials: 'include'
)

useEffect(() => 
    console.log('got here')
    fetchGet('/auth/token')
      .then(res => 
        console.log('res ', res)
       return res.json()
      )
      .then(res => 
        console.log('res.json ', res)
        return res.ok ? setJwtToken(res.token) : Promise.reject(res.statusText)
      )
      .catch(error => 
        console.log('err ', error)
        setError(error.toString())
      )
  , [])

在控制台中,我只能看到“到达这里”被记录下来,没有其他内容,并且前端因错误而崩溃:

DevTools 无法加载源地图:无法加载内容 数据:应用程序/json;charset=utf-8;base64,longTokenString...: 由于重新加载检查的页面而取消加载

我在这里做错了什么?

更新

我在这里发现了一个问题,我还有 2 个 useEffect 函数,它们在我得到结果之前就重定向了。我不知道为什么 useEffect 函数在我传递错误状态变量的地方在初始状态没有变化时运行?

这里是完整的代码:

const [jwtToken, setJwtToken] = useState(null)
const [error, setError] = useState(null)

useEffect(() => 
    fetchGet('/auth/token')
      .then(async res => 
        const data = await res.json()
        if (!res.ok) 
          const error = data?.message || res.statusText
          return Promise.reject(error)
        
        return data
      )
      .then((token) => setJwtToken(token))
      .catch(err => 
        console.log('err ', err)
        setError(err.toString())
      )
  , [])

  useEffect(() => 
    if (jwtToken) 
      // window.location.href = `/mypage.com?access/jwt?jwt=$jwtToken&return_to=`
      console.log(jwtToken)
    
  , [jwtToken])
  useEffect(() => 
    console.log(error)
    //window.location.href = '/login'
  , [error])

更新编号。 2:

const [jwtToken, setJwtToken] = useState('')
  const  search  = useLocation()

  useEffect(() => 
    fetchGet('/auth/token')
      .then(async res => 
        const data = await res.json()
        if (!res.ok) 
          const error = data?.message || res.statusText
          return Promise.reject(error)
        
        return data
      )
      .then((token) => setJwtToken(token))
      .catch(() => window.location.href = '/login')
  , [])

  useEffect(() => 
    const params = new URLSearchParams(search)
    const returnTo = params.get('return_to') ? `&return_to=$params.get('return_to')` : ''
    jwtToken !== '' ? window.location.href = `$url/jwt?jwt=$jwtToken$returnTo` : null
  , [jwtToken])

  return <p>Authenticating ...</p>

我已经删除了不必要的错误 useEffect 函数,但现在我得到了:

警告:无法对未安装的组件执行 React 状态更新。 这是一个空操作,但它表明您的应用程序中存在内存泄漏。 要修复,请取消 useEffect 中的所有订阅和异步任务 清理功能。

我收到此警告,并且在获取令牌后它也没有重定向。这次我做错了什么?

【问题讨论】:

您能console.log(fullUrl(uriPath)) 并报告结果...和/或显示fullUrl 的代码吗? 在注释掉来自其他 useEffects 的重定向后,您在控制台和网络选项卡中看到了什么? 请注意error 是一个对象,它会使useEffect 中的代码永远循环,因为它使用了浅层比较。 我怎样才能避免这种情况? @ShinaBR2 @Leff 你应该删除那个useEffect,并且应该在你的.catch(err =&gt; ) 中处理重定向。 【参考方案1】:

每个 useEffect 回调将在第一次挂载时调用。您应该包含一个简单的 if 语句,以确保在运行错误处理逻辑之前设置错误。

  useEffect(() => 
    if(error) 
      console.log(error)
      //window.location.href = '/login'
    
  , [error])

您的 API 的 CORS 配置可能存在问题。

Access-Control-Allow-Origin 响应标头必须设置为您的 React 应用程序的来源(对于凭据请求,它不能是 *)并且 Access-Control-Allow-Credentials 必须是 true。不包含它们将导致不透明的响应。

https://fetch.spec.whatwg.org/#cors-protocol-and-credentials

【讨论】:

【参考方案2】:

这是我完整的答案。这里的主要问题是错误地使用了useEffect,尤其是依赖数组中的对象。

我们先说这段代码

useEffect(() => 
  // TODO something with error
, [error]);

因为 error 是一个对象,而 React useEffect 使用浅比较,正如您在 this question 中看到的那样。它将使useEffect 中的代码永远运行。

下一部分,您会收到警告,因为您使用重定向的方式不正确。只需删除 useEffect 即可。

原因是,当我们遇到错误时,您的 catch 中的代码应该运行。除此之外,jwtToken 也将在那时更改。它会让你的应用在渲染过程完成之前被重定向。

【讨论】:

我得到那个错误 useEffect 是错误的,但是为什么我们设置令牌的另一个地方呢?如果提取时一切顺利,那useEffect不应该正常重定向吗? @Leff 因为你的重定向逻辑和catch 内部的逻辑是相同的。您只需要处理一次。 只有在出现错误并重定向到另一个地方时才会发生捕获,而带有令牌的 useEffect 只有在更改令牌并重定向到另一个地方时才会发生,所以我不确定你的意思是什么同样的逻辑? 只要jwtToken 更改正确,您的useEffect 就会更改。但问题是,您还可以在 useEffect 中处理重定向,这将使应用程序重定向 before 它完成重新渲染。这会导致您收到警告。我的意思相同的逻辑是“每当我们遇到错误时重定向”。您可以选择在catchuseEffect 内处理它。

以上是关于React - 即使状态变量没有变化,useEffect 也会运行的主要内容,如果未能解决你的问题,请参考以下文章

react父组件传入的属性没有让子组件收到的prop变化

当 graphql 变量的状态发生变化时,react-native 上的结果保持不变

即使该组件的道具或状态没有改变,也会调用 React shouldComponentUpdate()

react-native 横竖屏控制及状态变化监听

为啥即使我没有将 vuex 状态绑定到任何 html 输入元素,我的 vuex 状态也会在更改组件级别状态时发生变化?

我的 Redux 状态发生了变化,为啥 React 没有触发重新渲染?