【问题标题】:How to prevent set sate in unmounted component in React?如何防止在 React 中未安装的组件上设置状态?
【发布时间】:2021-11-05 18:49:48
【问题描述】:

我收到警告:“无法对未安装的组件执行 React 状态更新”,因此我尝试确定何时卸载我的组件,如下所示:

function ListStock() {

    let mounted = true;

    const [data, setData] = useState([]);
    const [search, setSearch] = useState();

    useEffect(() => {
        async function fetchData() {
            const {start_date, end_date} = search;
            const result = await getDataStock(start_date, end_date);
            
            if (result && mounted) {
                setData(result.data);  // only set a state when mounted = true
            }
        }

        fetchData();

        return () => {
            mounted = false; // set false on clean up
        }

    }, [search])

    const handleSearch = () => {
        ...
        setSearch({
            start_date: moment().subtract(1, 'month').format('YYYY-MM-DD'),
            end_date: moment().format('YYYY-MM-DD')
        });
    }

    return (
        <div>
            <input type="text" id="keyword">
            <input type="button" onlick={handleSearch} value="Search">
            {data}
        </div>
    )
}

通过这种方式,它可以解决该警告消息,但它显示另一个:

"从 React Hook 内部分配给 'mounted' 变量 每次渲染后 useEffect 都会丢失。为了保值 时间,将其存储在 useRef Hook 中,并将可变值保存在 '.current' 属性。否则,你可以直接移动这个变量 内部使用效果”

当我将 'mounted' 变量存储在 useRef 挂钩中时,我无法再进行搜索,因为 'mounted' 始终设置为“false”。

我的问题是:

  • 为什么当用户单击搜索按钮时会运行清理代码?我认为它仅在卸载组件时运行?
  • 使用远程 API 实现搜索作业的正确方法是什么?
  • 如果我将 ESLint 配置为忽略所有此类警告消息可以吗?

谢谢大家。

【问题讨论】:

    标签: reactjs


    【解决方案1】:

    问题是你做错了整个安装/卸载的事情。这是一个正确的实现:

    const mounted = useRef(false);
    
    useEffect(() => {
      mounted.current = true;
      return () => {
        mounted.current = false;
      };
    }, []); // Notice lack of dependencies
    

    在我继续之前,我可能应该向您推荐很棒的 react-use 库,它已经带有 useMountedState 钩子

    现在回到你的问题

    • 为什么当用户单击搜索按钮时会运行清理代码?我虽然它 仅在卸载组件时运行?

      直到我阅读了docs

      React 究竟何时清理效果? React 执行清理 当组件卸载时。然而,正如我们之前所了解的,效果 为每个渲染运行,而不仅仅是一次。这就是为什么 React 也会清理 在运行下一个效果之前从上一个渲染中添加效果 时间...

      所以你有它:清理功能在状态更改后发生的每次渲染之后运行,因此当search 更改时,需要重新渲染。

    • 使用远程 api 实现搜索作业的正确方法是什么?

      你这样做的方式很好,但如果你要每次都检查unmounted状态,你不妨使用我提到的库。

    • 如果我将 ESLint 配置为忽略所有此类警告可以吗 消息?

      不。修好它。这很容易

    【讨论】:

      【解决方案2】:

      您需要将 mounted = true; 存储在useRef 挂钩中,而不是使用变量。 UseRef 可以保存值,当值改变时它不会重新渲染页面。

      function ListStock() {
          const mounted = useRef(true);
      
          const [data, setData] = useState([]);
          const [search, setSearch] = useState();
      
          useEffect(() => {
              async function fetchData() {
                  const {start_date, end_date} = search;
                  const result = await getDataStock(start_date, end_date);
                  
                  if (result && mounted,current) {
                      setData(result.data);  // only set a state when mounted = true
                  }
              }
      
              fetchData();
      
              return () => {
                  mounted.current = false; // set false on clean up
              }
      
          }, [search])
      
          const handleSearch = () => {
              ...
              setSearch({
                  start_date: moment().subtract(1, 'month').format('YYYY-MM-DD'),
                  end_date: moment().format('YYYY-MM-DD')
              });
          }
      
          return (
              <div>
                  <input type="text" id="keyword">
                  <input type="button" onlick={handleSearch} value="Search">
                  {data}
              </div>
          )
      }
      

      希望上面的代码可以解决问题 1 和 2。第三个问题,我会说最好保留它,因为它表明出了什么问题。

      【讨论】:

      • 它不会按预期工作,因为每次“搜索”状态发生变化时 useEffect 函数都会重新运行,这会使清理代码运行,因此“已安装”= false
      【解决方案3】:

      在这种情况下,将mounted放在useEffect里面会更好, 一旦搜索改变,之前的请求应该被取消,

          const [data, setData] = useState([]);
          const [search, setSearch] = useState();
      
          useEffect(() => {
              let cancel = true;
              async function fetchData() {
                  const {start_date, end_date} = search;
                  const result = await getDataStock(start_date, end_date);
                  
                  if (result && !cancel) {
                      setData(result.data);  // only set a state when not canceled
                  }
              }
      
              fetchData();
      
              return () => {
                  cancel = true; // to cancel setState
              }
      
          }, [search])
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-06
        • 2019-02-17
        • 2020-08-27
        • 2023-03-26
        相关资源
        最近更新 更多