useEffect 模拟 componentWillUnmount 不返回更新状态

Posted

技术标签:

【中文标题】useEffect 模拟 componentWillUnmount 不返回更新状态【英文标题】:useEffect simulating componentWillUnmount does not return updated state 【发布时间】:2020-06-29 06:01:56 【问题描述】:

我有一个用 useState 初始化状态的功能组件,然后通过输入字段更改此状态。

然后我有一个模拟 componentWillUnmount 的 useEffect 挂钩,以便在卸载组件之前,将当前更新的状态记录到控制台。但是,记录的是初始状态而不是当前状态。

这是我想要做的简单表示(这不是我的实际组件):

import React,  useEffect, useState  from 'react';

const Input = () => 
    const [text, setText] = useState('aaa');

    useEffect(() => 
        return () => 
            console.log(text);
        
    , [])

    const onChange = (e) => 
        setText(e.target.value);
    ;

    return (
        <div>
            <input type="text" value=text onChange=onChange />
        </div>
    )


export default Input;

我将状态初始化为“初始”。然后我使用输入字段来改变状态,比如说我输入“新文本”。但是,卸载组件时,控制台会记录“初始”而不是“新文本”。

为什么会这样?如何在卸载时访问当前更新的状态?

非常感谢!

编辑:

将文本添加到 useEffect 依赖数组并不能解决我的问题,因为在我的实际场景中,我想做的是根据当前状态触发异步操作,这样做效率不高每次“文本”状态发生变化时。

我正在寻找一种仅在组件卸载之前获取当前状态的方法。

【问题讨论】:

【参考方案1】:

您已经有效地记住了初始状态值,因此当组件卸载时, 值就是返回的函数包含在其范围内的值。

Cleaning up an effect

清理功能在组件从 UI 中移除之前运行 以防止内存泄漏。此外,如果一个组件呈现多个 次(就像他们通常做的那样),之前的效果会在 执行下一个效果。在我们的示例中,这意味着一个新的 每次更新都会创建订阅。为了避免在 每次更新,请参阅下一节。

为了在调用清理函数时获得最新状态,您需要在依赖数组中包含text,以便更新函数。

Effect hook docs

如果你传递一个空数组([]),效果里面的props和state 将始终具有其初始值。同时通过[] 作为第二个 参数更接近于熟悉的componentDidMountcomponentWillUnmount心智模型,通常有更好的解决方案 以避免过于频繁地重新运行效果。

这意味着返回的“清理”函数仍然只访问上一个渲染周期的状态和道具。

编辑

useRef

useRef 返回一个可变引用对象,其.current 属性为 初始化为传递的参数 (initialValue)。返回的对象 将在组件的整个生命周期内持续存在。

...

可以方便地保留任何可变值,类似于您在类中使用实例字段的方式。

使用 ref 将允许您缓存可以在清理函数中访问的当前 text 引用。

/编辑

组件

import React,  useEffect, useRef, useState  from 'react';

const Input = () => 
  const [text, setText] = useState('aaa');

  // #1 ref to cache current text value
  const textRef = useRef(null);
  // #2 cache current text value
  textRef.current = text;

  useEffect(() => 
    console.log("Mounted", text);

    // #3 access ref to get current text value in cleanup
    return () => console.log("Unmounted", text, "textRef", textRef.current);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  , []);

  useEffect(() => 
    console.log("current text", text);
    return () => 
      console.log("previous text", text);
    
  , [text])

  const onChange = (e) => 
    setText(e.target.value);
  ;

  return (
    <div>
      <input type="text" value=text onChange=onChange />
    </div>
  )


export default Input;

使用返回的清理函数中的console.log,您会注意到输入的每次更改都会将先前的状态记录到控制台。

在这个演示中,我在效果中记录了当前状态在清理函数中的先前状态。请注意,清理函数在下一个渲染周期的当前日志之前首先记录。

【讨论】:

我的问题是,我真正需要做的是在组件卸载之前调度一个异步操作,所以在这种情况下,我不能使用你建议的方法,因为该操作会在每个状态改变的时间,这将是低效的。我正在寻找的是一种在卸载组件之前只获取当前状态一次的方法。 @edbgl 更新解决方案。 谢谢你,德鲁。我测试了您的解决方案,但初始值仍然是控制台日志而不是更新日志。在控制台中,我得到“Dismounted aaa”。 啊,对,我犯了一个错误。具有空依赖数组的效果挂钩将永远只有初始状态和道具。更新答案。您要解决什么具体的卸载方案? @EdmundoBiglia 得到了一个可行的解决方案、更新的答案和沙箱。

以上是关于useEffect 模拟 componentWillUnmount 不返回更新状态的主要内容,如果未能解决你的问题,请参考以下文章

在 useEffect 挂钩中使用 axios 取消令牌时如何修复失败的测试

关于useEffect中的无限循环

React hooks之useEffect

React Hooks(useEffect) 表单子任务

[react] 请说说什么是useEffect?

我应该如何测试使用 Typescript 进行 api 调用的 React Hook “useEffect”?