【问题标题】:setTimeout runs indefinetly inside useEffectsetTimeout 在 useEffect 内部无限运行
【发布时间】:2021-12-13 13:27:26
【问题描述】:

我正在尝试制作一个通知 toast 组件。而且我希望它在 2 秒后被移除(未显示在熨平板上)虽然它已被移除(未通过 top:-100 参数显示在屏幕上),但组件正在无限渲染。您可以从我放置在组件内的 console.log 以及带有 setTimeoutuseEffect 调用中看到它。

我的期望是 setTimeout 应该在 2 秒后运行 setShowState,然后 useEffect 应该进行清理并删除计时器。所以一切都恢复正常,直到showState 发生变化。

import React, {useEffect, useState} from 'react'
import I18n from '../../i18n'
import styled from 'styled-components'
import {createGlobalStyle} from 'styled-components'
import {useSelector} from 'react-redux'

const NotificationStyle = createGlobalStyle`
    @media (max-width: 500px) {
        .notification_mssg {
            left: 10px;
        }
    }
`

const Container = styled.div`
  color: white;
  position: fixed;
  top: ${(props) => props.top}px;
  right: 16px;
  z-index: 2000;
  transition: top 0.5s ease;
`

const NoticitactionIcon = styled.div`
  float: left;
  font-size: 27px;
  width: 40px;
  height: 40px;
  text-align: center;
`

const NotificationMessage = styled.span`
  padding: 10px;
  line-height: 40px;
`

function NotificationAlertRoot(props) {

  const create_notification = useSelector((state) => state.notifications.create_notification)

  const {message, code} = create_notification.success_info
  const [showState, setShowState] = useState({top: -100, msg: message, bgColor: '#444'})

  // show notification
  useEffect(() => {
    setShowState({top: 96, msg: I18n.t(message), bgColor: backgroundColor(code)})
  }, [message, code])

  console.log('amIrendered', showState) // although showState doesn't change, component is getting rendered infinitely :/

  // hide notification after 2 seconds
  useEffect(() => {
    const timerId = setTimeout(() => {
      setShowState({
        top: -100,
        msg: '',
        bgColor: `#ffffff00`,
      })
      console.log("timerId", timerId) // I see timerId is changing so the problem most probably in this useEffect call.
    }, 2000)

    return () => {
      clearTimeout(timerId)
    }
  }, [showState])

  const notificationIcon = (bgColor) => {
    switch (bgColor) {
      case '#32c786':
        return (
          <NoticitactionIcon style={{background: '#2aa872'}}>
            <i className="zmdi zmdi-info" />
          </NoticitactionIcon>
        )
      case '#ffc721':
        return (
          <NoticitactionIcon style={{background: '#fabb00'}}>
            <i className="zmdi zmdi-alert-triangle" />
          </NoticitactionIcon>
        )
      case '#ff6b68':
        return (
          <NoticitactionIcon style={{background: '#ff4642'}}>
            <i className="zmdi zmdi-alert-circle" />
          </NoticitactionIcon>
        )
      default:
        return <span></span>
    }
  }

  function backgroundColor(code) {
    switch (Math.floor(code / 100)) {
      case 2:
        return '#32c786'
      case 3:
        return '#ffc721'
      case 4:
        return '#ff6b68'
      case 5:
        return '#ff6b68'
      default:
        return '#444'
    }
  }
  return (
    <React.Fragment>
      <NotificationStyle />
      <Container
        className="notification_mssg"
        top={showState.top}
        style={{background: showState.bgColor}}
      >
        {notificationIcon(showState.bgColor)}
        <NotificationMessage>{showState.msg}</NotificationMessage>
      </Container>
    </React.Fragment>
  )
}

export default NotificationAlertRoot


你知道上面有什么问题吗?

【问题讨论】:

  • 尝试设置'-100px'
  • @Robert 这无关紧要,因为他在样式化的 comp 中应用了 px。
  • 它知道 px 不是 em, rem, ch 吗?
  • 我认为这是因为您正在更新 useEffect 中的 showState,所以在它清除两秒钟后它会再次触发,因为它会注意到更改。
  • 通知消失不是问题。它在 2 秒后很好地消失,但组件被渲染得不确定。从 console.log 中可以看到 timerId 每 2 秒获取一个新的 timerId

标签: javascript reactjs react-redux use-effect


【解决方案1】:

我猜这个问题来自你的依赖数组。你的useEffect 依赖于showState,每次,当setShowState 被称为showState 时,你在useEffect 中调用setShowState,然后再一次,你的useEffect 被调用(它依赖于ShowState),再次调用setShowState 并且...... 无限循环!

【讨论】:

  • 首先 useEffect 将 showState 设置为 setShowState,然后遵循来自 redux 存储的消息和代码,这很好。第二个 useEffect 跟随 showState 完成它的工作(两秒钟后删除它)所以我必须在第二个 useEffect 上跟随它。当我还在第二个 useEffect 中使用 console.log showState 时,我看到 showState 也没有改变。
【解决方案2】:

我找到了问题的根源。有时你太专注于某件事而忘记了 useEffect 的小细节。将对象作为依赖数组提供给 useEffect 总是很危险的。依赖数组值应该是简单值。所以现在我引入了一个只有布尔值的新状态(标志,setFlag),我制作了第二个 useEffect 只是为了遵循这个简单的值。现在一切正常。


import React, {useEffect, useState} from 'react'
import I18n from '../../i18n'
import styled from 'styled-components'
import {createGlobalStyle} from 'styled-components'
import {useSelector} from 'react-redux'

const NotificationStyle = createGlobalStyle`
    @media (max-width: 500px) {
        .notification_mssg {
            left: 10px;
        }
    }
`

const Container = styled.div`
  color: white;
  position: fixed;
  top: ${(props) => props.top}px;
  right: 16px;
  z-index: 2000;
  transition: top 0.5s ease;
`

const NotificationIcon = styled.div`
  float: left;
  font-size: 27px;
  width: 40px;
  height: 40px;
  text-align: center;
`

const NotificationMessage = styled.span`
  padding: 10px;
  line-height: 40px;
`

function NotificationAlertRoot(props) {

  const create_notification = useSelector((state) => state.notifications.create_notification)

  const {message, code} = create_notification.success_info
  const [showState, setShowState] = useState({top: -100, msg: message, bgColor: '#444'})
  const [flag, setFlag] = useState(false) // when you follow the showState at the second useEffect you have an infinite loop. Because it is an object.

  // show notification
  useEffect(() => {
    setShowState({top: 96, msg: I18n.t(message), bgColor: backgroundColor(code)})
    setFlag(true)
  }, [message, code])

  // hide notification after 2 seconds
  useEffect(() => {
    const timerId = setTimeout(() => {
      setShowState({top: -100,msg: '', bgColor: `#ffffff00`})
      setFlag(false)
    }, 2000)

    return () => {
      clearTimeout(timerId)
    }
  }, [flag]) // showState

  const notificationIcon = (bgColor) => {
    switch (bgColor) {
      case '#32c786':
        return (
          <NotificationIcon style={{background: '#2aa872'}}>
            <i className="zmdi zmdi-info" />
          </NotificationIcon>
        )
      case '#ffc721':
        return (
          <NotificationIcon style={{background: '#fabb00'}}>
            <i className="zmdi zmdi-alert-triangle" />
          </NotificationIcon>
        )
      case '#ff6b68':
        return (
          <NotificationIcon style={{background: '#ff4642'}}>
            <i className="zmdi zmdi-alert-circle" />
          </NotificationIcon>
        )
      default:
        return <span></span>
    }
  }

  const backgroundColor = (code) => {
    switch (Math.floor(code / 100)) {
      case 2:
        return '#32c786'
      case 3:
        return '#ffc721'
      case 4:
        return '#ff6b68'
      case 5:
        return '#ff6b68'
      default:
        return '#444'
    }
  }
  return (
    <React.Fragment>
      <NotificationStyle />
      <Container
        className="notification_mssg"
        top={showState.top}
        style={{background: showState.bgColor}}
      >
        {notificationIcon(showState.bgColor)}
        <NotificationMessage>{showState.msg}</NotificationMessage>
      </Container>
    </React.Fragment>
  )
}

export default NotificationAlertRoot





【讨论】:

  • 我建议使用 JSON.stringify(showState) 作为依赖项。这样您就不会将对象用作依赖项,也不需要标志。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-24
  • 2020-07-01
  • 2019-04-14
  • 2020-07-09
  • 1970-01-01
  • 2020-11-05
相关资源
最近更新 更多