[英]React Hooks - useEffect fires even though the state did not change
我在我的组件中设置了一个效果,如果另一个状态属性发生变化,它会改变视图。 但是由于某种原因,当组件挂载时,即使detailIndex
的值没有改变,效果也会运行。
const EventsSearchList = () => {
const [view, setView] = useState('table');
const [detailIndex, setDetailIndex] = useState(null);
useEffect(() => {
console.log('onMount', detailIndex);
// On mount shows "null"
}, []);
useEffect(
a => {
console.log('Running effect', detailIndex);
// On mount shows "null"!! Should not have run...
setView('detail');
},
[detailIndex]
);
return <div>123</div>;
};
为什么会这样?
更新:如果不清楚,我正在尝试在组件更新时运行效果,因为detailIndex
更改。 不是在安装时。
默认情况下,React Hooks 中的useEffect
在每次渲染时执行,但您可以在函数中使用第二个参数来定义何时再次执行效果。 这意味着该函数始终在挂载时执行。 在您的情况下,您的第二个useEffect
将在开始时和detailIndex
更改时运行。
更多信息: https : //reactjs.org/docs/hooks-effect.html
来源:
有经验的 JavaScript 开发人员可能会注意到,传递给 useEffect 的函数在每次渲染时都会有所不同。 [...] 如果某些值在重新渲染之间没有改变,你可以告诉 React 跳过应用效果。 为此,将数组作为可选的第二个参数传递给 useEffect: [...]
就我而言,即使我在 useEffect() 中使用了第二个参数,组件也会不断更新,并且我正在打印该参数以确保它没有改变,也没有改变。 问题是我正在使用 map() 渲染组件,并且在某些情况下键发生了变化,如果键发生了变化,对于 react 来说,它是一个完全不同的对象。
您可以添加第二个守卫,检查 detailIndex 是否已从初始值更改为 -1
useEffect(
a => {
if(detailIndex != -1)
{ console.log('Running effect', detailIndex);
// On mount shows "null"!! Should not have run...
setView('detail');
}},
[detailIndex]);
当且仅当useEffect
的第二个参数状态通过引用(不是值)改变时,效果才会触发。
import React, { useState, useEffect } from 'react';
function App() {
const [x, setX] = useState({ a: 1 });
useEffect(() => {
console.log('x');
}, [x]);
setInterval(() => setX({ a: 1 }), 1000);
return <div></div>
}
}
export default App;
在此代码片段中,日志每秒打印一次,因为每次调用setX({a: 1})
,都会使用对新对象的新引用更新x
状态。
如果setX({a: 1})
被setX(1)
替换,则效果不会定期触发。
确保代码只在“真实”状态更新而不是在第一次渲染时运行的一种方法是分配一个最初为false
的变量,只有在第一次渲染后才变为true
,这个变量自然应该是一个钩子,所以它将在组件的整个生命周期内被记录下来。
const {useEffect, useRef, useState} = React const App = () => { const mountedRef = useRef() // ← the "flag" const [value, setValue] = useState(false) // simulate a state change // with the trick useEffect(() => { if (mountedRef.current){ // ← the trick console.log("trick: changed") } }, [value]) // without the trick useEffect(() => { console.log("regular: changed") }, [value]) // fires once & sets "mountedRef" ref to "true" useEffect(() => { console.log("rendered") mountedRef.current = true // update the state after some time setTimeout(setValue, 1000, true) }, []) return null } ReactDOM.render(<App/>, document.getElementById("root"))
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <div id="root"></div>
useEffect
总是在初始渲染之后运行。
从 文档:
useEffect 是否在每次渲染后运行? 是的! 默认情况下,它会在第一次渲染之后和每次更新之后运行。 (我们稍后将讨论如何自定义它。)与其考虑“安装”和“更新”,您可能会发现更容易认为效果发生在“渲染之后”。 React 保证 DOM 在它运行效果时已经更新。
关于您的代码,由于未指定任何依赖项,因此以下内容只会运行一次( useEffect
将可选的依赖项数组作为第二个参数):
useEffect(() => {
console.log('onMount', detailIndex);
// On mount shows "null" -> since default value for detailIndex is null as seen above
// const [detailIndex, setDetailIndex] = useState(null);
}, []);
这将只运行在detailIndex
改变(尝试调用setDetailIndex
以前的内useEffect
):
useEffect(() =>
// ... effect code
}, [detailIndex]);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.