最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • React之如何阻止在已卸载的组件上进行setState

    正文概述 掘金(摸鱼师保护协会会长)   2021-01-10   630

      一般来说,偶尔出现的这个Warning确实不会带来严重的性能问题,但是试想如果是setInterval的句柄没有被正确在卸载周期中进行清理,那即便你的组件销毁了,它也会持续地生效,不仅会造成memory leak,亦会拖慢你项目的响应速度。所以,作为一个严谨的开发者来说,我们在实现逻辑时,就须要先行考虑到这些问题。另外对于一些强迫症同学来说,肯定不会希望每次打开控制台看到一坨红屏,更别说我们在开发过程中,还会经常遇到另一个常见的对数组结构生成渲染Element缺少key值的Warning场景。

      在一波科学上网后,我大概得到了两种处理方式,“治标”“治本”

    治标

      治标法本质上是在你的class组件或者hooks函数组件中声明一个哨兵变量,具体是用什么方式声明,如声明在实例属性useRefuseEffect的局部变量上都无所谓,它们都能达到同样的效果。

      我们就以一个获取后台日志的场景为例。

      class组件:

    export default class LogList extends PureComponent {
        _isMounted = false
        componentDidMount() {
            this._isMounted = true
        }
        componentWillUnmount() {
            this._isMounted = false
        }
        fetchLogList = id => {
            return axios.get(`/fetchList/${id}`).then(res => {
                if (this._isMounted) {
                    // setState动作...
                }
            })
        }
        render() {
            // 渲染
        }
    }
    

      hooks组件:

    // useRef保存哨兵变量
    export default function LogList() {
        const _isMounted = useRef(false)
        const [logList, setLogList] = useState([])
        fetchLogList = id => {
            return axios.get(`/fetchList/${id}`).then(res => {
                if (_isMounted.current) {
                    // setLogList...
                }
            })
        }
        useEffect(() => {
            _isMounted.current = true
            return () => {
                _isMounted.current = false
            }
        }, [])
        return (
            // 渲染
        )
    }
    
    // useEffect内部声明哨兵变量
    export default function LogList() {
        const [logList, setLogList] = useState([])
        fetchLogList = id => {
            return axios.get(`/fetchList/${id}`)
        }
        useEffect(() => {
            let _isMounted = true
            fetchLogList(1).then(res => {
                if (_isMounted) {
                    // setLogList...
                }
            })
            return () => {
                _isMounted = false
            }
        }, [])
        return (
            // 渲染
        )
    }
    

    治本

      治本要怎么治呢?其实在仔细观察治标中的操作后,我们发现我们都在当前组件上挂载了一个“脏东西”。作为一个组件本身的定位来说,它不再纯粹了,我们为了处理这种异步渲染的Warning而在组件本身上加东西是不太合适的。调整的核心思路在于**“解耦”**。

      参考js本身的timer,我们可以发现它们都会返回一个handler句柄用于之后的取消任务。那么诸如ajax请求之类的promise返回也是同理,问题就可以转移成:我们如何提供一个可以取消promise的方法? 从设计本身而言,这种异步等待的任务都应该具有一个取消的机制,等太久了我是不是应该直接将任务取消再主动发起?另外任务的等待处理逻辑本身也不应该放到组件属性上去做,会使得一个组件设计上职能不集中,看上去就很难受。

      大致方法就是实现一个高阶函数,同时返回封装后的新Promise实例以及支持取消该Promise的cancel方法:

    const makeCancelable = (promise) => {
        let hasCanceled_ = false;
    
        const wrappedPromise = new Promise((resolve, reject) => {
            promise.then(
                val => hasCanceled_ ? reject({ isCanceled: true }) : resolve(val),
                error => hasCanceled_ ? reject({ isCanceled: true }) : reject(error)
            );
        });
    
        return {
            promise: wrappedPromise,
            cancel() {
                hasCanceled_ = true;
            },
        };
    };
    

      之前的问题就可以修改为如下的样子:

    import { makeCancelable } from '@/utils'
    
    export default function LogList() {
        const [logList, setLogList] = useState([])
        fetchLogList = id => {
            return axios.get(`/fetchList/${id}`)
        }
        useEffect(() => {
            const { promise, cancel } = makeCancelable(fetchLogList(1))
            promise.then(res => {
                // setLogList...
            })
            return () => {
                cancel()
            }
        }, [])
        return (
            // 渲染
        )
    }
    

      P.S. 实际上我们也可以再换个思路,通过将状态交由react-reduxstore掌控,组件拆分为无状态组件进行显示渲染,外层的业务组件进行通过dispatch派发action,中间件层进行异步动作,一样可以处理该问题。


    起源地下载网 » React之如何阻止在已卸载的组件上进行setState

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元