仅对 componentWillUnmount 反应挂钩 useEffect() 清理?

react hooks useEffect() cleanup for only componentWillUnmount?

让我解释一下这段代码的结果,以便轻松地提出我的问题。

const ForExample = () => {
    const [name, setName] = useState('');
    const [username, setUsername] = useState('');

    useEffect(() => {
        console.log('effect');
        console.log({
            name,
            username
        });

        return () => {
            console.log('cleaned up');
            console.log({
                name,
                username
            });
        };
    }, [username]);

    const handleName = e => {
        const { value } = e.target;

        setName(value);
    };

    const handleUsername = e => {
        const { value } = e.target;

        setUsername(value);
    };

    return (
        <div>
            <div>
                <input value={name} onChange={handleName} />
                <input value={username} onChange={handleUsername} />
            </div>
            <div>
                <div>
                    <span>{name}</span>
                </div>
                <div>
                    <span>{username}</span>
                </div>
            </div>
        </div>
    );
};

ForExample component 安装时,'effect' 将被记录。这与componentDidMount().

有关

每当我更改名称输入时,'effect' 和 'cleaned up' 都会被记录。反之亦然,自从我将 [username] 添加到 useEffect() 的第二个参数后,每当我更改用户名输入时都不会记录任何消息。这与componentDidUpdate()

有关

最后,当 ForExample component 卸载时,'cleaned up' 将被记录。这与componentWillUnmount().

有关

我们都知道。

总而言之,每当重新渲染组件(包括卸载)时都会调用 'cleaned up'

如果我想让这个组件只在卸载时记录'cleaned up',我只需要将useEffect()的第二个参数更改为[]

但是如果我将 [username] 更改为 []ForExample component 将不再实现名称输入的 componentDidUpdate()

我想做的是,让组件同时支持 componentDidUpdate() 仅用于名称输入和 componentWillUnmount()。 (仅在卸载组件时记录 'cleaned up')

由于清理不依赖于 username,您可以将清理放在一个单独的 useEffect 中,它给出一个空数组作为第二个参数。

例子

const { useState, useEffect } = React;

const ForExample = () => {
  const [name, setName] = useState("");
  const [username, setUsername] = useState("");

  useEffect(
    () => {
      console.log("effect");
    },
    [username]
  );

  useEffect(() => {
    return () => {
      console.log("cleaned up");
    };
  }, []);

  const handleName = e => {
    const { value } = e.target;

    setName(value);
  };

  const handleUsername = e => {
    const { value } = e.target;

    setUsername(value);
  };

  return (
    <div>
      <div>
        <input value={name} onChange={handleName} />
        <input value={username} onChange={handleUsername} />
      </div>
      <div>
        <div>
          <span>{name}</span>
        </div>
        <div>
          <span>{username}</span>
        </div>
      </div>
    </div>
  );
};

function App() {
  const [shouldRender, setShouldRender] = useState(true);

  useEffect(() => {
    setTimeout(() => {
      setShouldRender(false);
    }, 5000);
  }, []);

  return shouldRender ? <ForExample /> : null;
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

您可以使用多个 useEffect()。

例如,如果我的变量是 data1,我可以在我的组件中使用所有这些:

useEffect( () => console.log("mount"), [] );
useEffect( () => console.log("data1 update"), [ data1 ] );
useEffect( () => console.log("any update") );
useEffect( () => () => console.log("data1 update or unmount"), [ data1 ] );
useEffect( () => () => console.log("unmount"), [] );
function LegoComponent() {

  const [lego, setLegos] = React.useState([])

  React.useEffect(() => {
    let isSubscribed = true
    fetchLegos().then( legos=> {
      if (isSubscribed) {
        setLegos(legos)
      }
    })
    return () => isSubscribed = false
  }, []);

  return (
    <ul>
    {legos.map(lego=> <li>{lego}</li>)}
    </ul>
  )
}

在上面的代码中,fetchLegos 函数 returns 一个承诺。我们可以通过在 useEffect 范围内设置条件来“取消”承诺,防止应用程序在组件卸载后设置状态。

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

useEffect 在其自己的范围内被隔离并相应地进行渲染。 图片来自https://reactjs.org/docs/hooks-custom.html

我没有创建太多复杂的函数和方法,而是创建一个事件侦听器并自动为我完成挂载和卸载,而不必担心手动执行。这是一个例子。

//componentDidMount
useEffect( () => {

    window.addEventListener("load",  pageLoad);

    //component will unmount
    return () => {
       
        window.removeEventListener("load", pageLoad);
    }

 });

现在这部分已经完成,我只是 运行 我想要从 pageLoad 函数中得到的任何东西,就像这样。

const pageLoad = () =>{
console.log(I was mounted and unmounted automatically :D)}

为了补充已接受的答案,我有一个类似的问题,并使用类似的方法和下面的人为示例解决了它。在这种情况下,我需要在 componentWillUnmount 上记录一些参数,并且如原始问题中所述,我不希望每次参数更改时都记录它。

const componentWillUnmount = useRef(false)

// This is componentWillUnmount
useEffect(() => {
    return () => {
        componentWillUnmount.current = true
    }
}, [])

useEffect(() => {
    return () => {
        // This line only evaluates to true after the componentWillUnmount happens 
        if (componentWillUnmount.current) {
            console.log(params)
        }
    }

}, [params]) // This dependency guarantees that when the componentWillUnmount fires it will log the latest params

使用自定义 js 事件,您可以模拟卸载 componentWillUnmount,即使在具有依赖性时也是如此。

问题:

    useEffect(() => {
    //Dependent Code
    return () => {
        // Desired to perform action on unmount only 'componentWillUnmount' 
        // But it does not
        if(somethingChanged){
            // Perform an Action only if something changed
        }
    }
},[somethingChanged]);

解法:

// Rewrite this code  to arrange emulate this behaviour

// Decoupling using events
useEffect( () => {
    return () => {
        // Executed only when component unmounts,
        let e = new Event("componentUnmount");
        document.dispatchEvent(e);
    }
}, []);

useEffect( () => {
    function doOnUnmount(){
        if(somethingChanged){
            // Perform an Action only if something changed
        }
    }

    document.addEventListener("componentUnmount",doOnUnmount);
    return () => {
        // This is done whenever value of somethingChanged changes
        document.removeEventListener("componentUnmount",doOnUnmount);
    }

}, [somethingChanged])

注意事项: useEffects 必须按顺序排列,useEffect 必须按顺序排列写在前面,这是为了避免事件被移除后被调用。

这是我的解决方案,概括为自定义挂钩:

import React, { useEffect, useRef } from 'react';

const useUnmountEffect = (effect, dependencies) => {
  if (typeof effect !== 'function') {
    console.error('Effect must be a function');
  }

  const componentWillUnmount = useRef(false)

  useEffect(() => () => {
    componentWillUnmount.current = true
  }, []);

  useEffect(() => () => {
    if (componentWillUnmount.current) {
      effect?.();
    }
  }, dependencies);
}

export default useUnmountEffect;

怎么样:

function useOnUnmount(callback: () => void) {
    const onUnmount = useRef<(() => void) | null>(null);
    onUnmount.current = callback;

    useEffect(() => {
        return () => onUnmount.current?.();
    }, []);
}

useOnUnmount(() => {
    console.log("unmount", props);
});