我一直在学习React,我读到useEffect
返回的函数是要进行清理的,而在组件卸载时,React会执行清理。
所以我做了一些实验,但是在下面的示例中发现,每次重新发布组件时都会调用该函数,而不是每次从DOM上卸载该组件时即调用console.log("unmount");
组件重新呈现。
那是为什么?
function Something({ setShow }) {
const [array, setArray] = useState([]);
const myRef = useRef(null);
useEffect(() => {
const id = setInterval(() => {
setArray(array.concat("hello"));
}, 3000);
myRef.current = id;
return () => {
console.log("unmount");
clearInterval(myRef.current);
};
}, [array]);
const unmount = () => {
setShow(false);
};
return (
<div>
{array.map((item, index) => {
return (
<p key={index}>
{Array(index + 1)
.fill(item)
.join("")}
</p>
);
})}
<button onClick={() => unmount()}>close</button>
</div>
);
}
function App() {
const [show, setShow] = useState(true);
return show ? <Something setShow={setShow} /> : null;
}
答案 0 :(得分:1)
React在组件卸载时执行清理。
我不确定您在哪里阅读此书,但此说法不正确。 React performs the cleanup when the dependencies to that hook changes and the effect hook needs to run again with new values。此行为是为了维持视图对更改数据的反应性。以官方示例为例,假设某个应用程序订阅了朋友个人资料中的状态更新。作为您的好朋友,您决定不与他们成为朋友,并与其他人成为朋友。现在,该应用程序需要退订上一个朋友的状态更新,并收听您新朋友的更新。通过useEffect
的工作方式,这很自然并且很容易实现。
useEffect(() => {
chatAPI.subscribe(props.friend.id);
return () => chatAPI.unsubscribe(props.friend.id);
}, [ props.friend.id ])
通过在依赖关系列表中包含好友ID,我们可以指示挂钩仅在好友ID更改时才需要运行。
在您的示例中,您已在依赖项列表中指定了array
,并且您将以设定的间隔更改数组。每次更改数组时,挂钩都会重新运行。
只需从依赖项列表中删除数组并使用setState
钩子的回调版本,即可实现正确的功能。回调版本始终在状态的先前版本上运行,因此无需在每次数组更改时刷新钩子。
useEffect(() => {
const id = setInterval(() => setArray(array => [ ...array, "hello" ]), 3000);
return () => {
console.log("unmount");
clearInterval(id);
};
}, []);
由于创建清理函数时该值在(捕获)时已关闭(捕获),因此一些其他反馈将是直接在clearInterval
中使用id。无需将其保存到参考。
答案 1 :(得分:0)
由于第二个参数[array]
,我看一下代码可以猜到它。您正在更新它,因此它将调用重新渲染。尝试设置一个空数组。
每次状态更新都会调用重新渲染和卸载,并且该数组正在更改。
答案 2 :(得分:0)
React文档对此有一个explanation section。
简而言之,原因是因为这种设计可以防止过时的数据和更新错误。
React中的useEffect
挂钩旨在处理初始渲染和任何后续渲染(here's more about it)。
效果是通过它们的依赖性来控制的,而不是通过使用它们的组件的生命周期来控制的。
效果更改的任何时间依赖性,useEffect
都会清除先前的效果并运行新的效果。
这种设计更具可预测性-each render has its own independent (pure) behavioral effect。这样可以确保用户界面始终显示正确的数据(因为React心理模型中的用户界面是特定渲染状态的屏幕截图)。
我们控制效果的方法是通过它们的依赖性。
为防止清理在每个渲染器上运行,我们只需不必更改效果的依赖性。
具体来说,清理工作是因为array
发生了变化,即Object.is(oldArray, newArray) === false
useEffect(() => {
// ...
}, [array]);
// ^^^^^ you're changing the dependency of the effect
您正在使用以下行引起此更改:
useEffect(() => {
const id = setInterval(() => {
setArray(array.concat("hello")); // <-- changing the array changes the effect dep
}, 3000);
myRef.current = id;
return () => {
clearInterval(myRef.current);
};
}, [array]); // <-- the array is the effect dep
答案 3 :(得分:0)
似乎是预期的。根据此处的文档,useAffects
在首次渲染,每次更新和卸载后调用。
https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
提示
如果您熟悉React类的生命周期方法,可以考虑 useEffect Hook作为componentDidMount,componentDidUpdate和之前的版本 componentWillUnmount组合。
答案 4 :(得分:0)
正如其他人所说,useEffect取决于useEffect的第二个参数中指定的“数组”的更改。因此,通过将其设置为空数组,这将有助于在安装组件时触发一次useEffect。
这里的技巧是更改数组的先前状态。
setArray((arr) => arr.concat("hello"));
见下文:
useEffect(() => {
const id = setInterval(() => {
setArray((arr) => arr.concat("hello"));
}, 3000);
myRef.current = id;
return () => {
console.log("unmount");
clearInterval(myRef.current);
};
}, []);
我分叉了您的CodeSandbox进行演示: https://codesandbox.io/s/heuristic-maxwell-gcuf7?file=/src/index.js