繁体   English   中英

无法清理 setTimeout in useEffect unmount cleanup

[英]can't clean up setTimeout in useEffect unmount cleanup

我正在尝试制作一种 flash 消息显示器,以在一定时间内在顶部显示成功、错误、警告消息。

我已经使用 useRef 挂钩来存储超时,以便在超时完成之前卸载组件时清除它。

一切都按预期工作,除非组件在超时回调之前卸载,它不会清除确实正在尝试 setState 的超时

警告:无法在未安装的组件上执行 React state 更新

 import React, { useEffect, useRef, useState } from 'react' import SuccessGreen from '../../assets/SuccessGreen.svg' import Cross from '../../assets/Cancel.svg' import WarningExclamation from '../../assets/WarningExclamation.svg' const ICONS_MAP = { "warning": WarningExclamation, "success": SuccessGreen, "error": "" } export const FlashMessages = ({ duration=5000, closeCallback, pauseOnHover=false, messageTheme='warning', typoGraphy={className: 'text_body'}, firstIcon=true, ...props }) => { const [isDisplayable, setIsDisplayable] = useState(true) const resumedAt = useRef(null) const remainingDuration = useRef(duration) const countDownTimer = useRef(null) useEffect(() => { countDownTimer.current = resumeDuration() console.log(countDownTimer, "From mount") return () => {clearTimeout(countDownTimer.current)} }, []) const resumeDuration = () => { clearTimeout(countDownTimer.current) resumedAt.current = new Date() return setTimeout(() => forceCancel(), remainingDuration.current) } const pauseDuration = () => { if(pauseOnHover){ clearTimeout(countDownTimer.current) remainingDuration.current = remainingDuration.current - (new Date() - resumedAt.current) } } const forceCancel = () => { console.log(countDownTimer, "From force") clearTimeout(countDownTimer.current); setIsDisplayable(false); closeCallback(null); } return isDisplayable? ( <div onMouseEnter={pauseDuration} onMouseLeave={resumeDuration} className={`flash_message_container ${messageTheme} ${typoGraphy.className}`} style={props.style}> { firstIcon? (<img src={ICONS_MAP[messageTheme]} style={{marginRight: 8, width: 20}} />): null } <div style={{marginRight: 8}}>{props.children}</div> <img src={Cross} onClick={forceCancel} style={{cursor: 'pointer', width: 20}}/> </div> ):null }

我试图模仿这个 npm package https://github.com/danielsneijers/whiact-flash/index.jsxage/

我认为问题在于当mouseleave事件发生时, resumeDuration返回的超时 id 没有保存在countDownTimer.current中,因此在 useEffect 返回的清理useEffect中没有清除超时。

您可以修改resumeDuration以将超时 id 保存到countDownTimer.current而不是返回它:

countDownTimer.current = setTimeout(() => forceCancel(), remainingDuration.current)

然后,在useEffect中,只需调用resumeDuration ,因此组件将如下所示:

import React, { useEffect, useRef, useState } from 'react'
import SuccessGreen from '../../assets/SuccessGreen.svg'
import Cross from '../../assets/Cancel.svg'
import WarningExclamation from '../../assets/WarningExclamation.svg'

const ICONS_MAP = {
    "warning": WarningExclamation,
    "success": SuccessGreen,
    "error": ""
}

export const FlashMessages = ({
    duration=5000,
    closeCallback,
    pauseOnHover=false,
    messageTheme='warning',
    typoGraphy={className: 'text_body'},
    firstIcon=true,
    ...props
}) => {
    const [isDisplayable, setIsDisplayable] = useState(true)
    const resumedAt = useRef(null)
    const remainingDuration = useRef(duration)
    const countDownTimer = useRef(null)

    useEffect(() => {
        resumeDuration()
        console.log(countDownTimer, "From mount")
        return () => {clearTimeout(countDownTimer.current)}
    }, [])

    const resumeDuration = () => {
        clearTimeout(countDownTimer.current)
        resumedAt.current = new Date()
        countDownTimer.current = setTimeout(() => forceCancel(), remainingDuration.current)
    }

    const pauseDuration = () => {
        if(pauseOnHover){
            clearTimeout(countDownTimer.current)
            remainingDuration.current = remainingDuration.current - (new Date() - resumedAt.current)
        }
    }

    const forceCancel = () => {
        console.log(countDownTimer, "From force")
        clearTimeout(countDownTimer.current);
        setIsDisplayable(false);
        closeCallback(null);
    }

    return isDisplayable ? (
        <div onMouseEnter={pauseDuration} onMouseLeave={resumeDuration}
            className={`flash_message_container ${messageTheme} ${typoGraphy.className}`} style={props.style}>
            {   firstIcon ? (<img src={ICONS_MAP[messageTheme]} style={{marginRight: 8, width: 20}} />) : null   }
            <div style={{marginRight: 8}}>{props.children}</div>
            <img src={Cross} onClick={forceCancel} style={{cursor: 'pointer', width: 20}}/>
        </div>
    ):null
}

然后它将模仿https://github.com/danielsneijers/react-flash-message/blob/master/src/index.jsx的逻辑

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM