仅对 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);
});
让我解释一下这段代码的结果,以便轻松地提出我的问题。
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);
});