React Hooks - 即使状态没有改变,useEffect 也会触发
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>;
};
为什么会这样?
UPDATE: 万一不清楚,我想做的是运行组件更新时的效果,因为detailIndex
变化了。不是在安装时。
useEffect
在初始渲染后总是 运行。
来自docs:
Does useEffect run after every render? Yes! By default, it runs both
after the first render and after every update. (We will later talk
about how to customize this.) Instead of thinking in terms of
“mounting” and “updating”, you might find it easier to think that
effects happen “after render”. React guarantees the DOM has been
updated by the time it runs the effects.
关于您的代码,由于未指定依赖项,因此以下代码只会 运行 一次(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
更改时触发(尝试在之前的 useEffect
中调用 setDetailIndex
):
useEffect(() =>
// ... effect code
}, [detailIndex]);
默认情况下,React Hooks 中的 useEffect
会在每次渲染时执行,但您可以使用函数中的第二个参数来定义效果何时再次执行。这意味着函数 always 在挂载时执行。在您的情况下,您的第二个 useEffect
将在开始和 detailIndex
更改时为 运行。
更多信息:https://reactjs.org/docs/hooks-effect.html
来源:
Experienced JavaScript developers might notice that the function passed to useEffect is going to be different on every render. [...] You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect: [...]
在我的例子中,即使我在 useEffect() 中使用了第二个参数并且我正在打印参数以确保它没有改变并且它没有改变,组件仍然在更新。问题是我正在使用 map() 渲染组件,并且在某些情况下键发生了变化,如果键发生了变化,对于反应来说它是一个完全不同的对象。
你可以添加第二个守卫,检查 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)
,效果将不会定期触发。
确保代码仅在 "real" 状态更新上运行并且 not 也在第一次渲染上运行的一种方法是分配一个最初是 false
的变量,在第一次渲染 之后只变成 true
,这个变量自然应该是 hook,因此在组件的整个生命周期中它将 memo-ed。
3个不同的比较useEffect
:
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>
我在我的组件中设置了一个效果,如果另一个状态属性发生变化,它会改变视图。但是由于某些原因,当组件挂载时,效果是运行,即使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>;
};
为什么会这样?
UPDATE: 万一不清楚,我想做的是运行组件更新时的效果,因为detailIndex
变化了。不是在安装时。
useEffect
在初始渲染后总是 运行。
来自docs:
Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
关于您的代码,由于未指定依赖项,因此以下代码只会 运行 一次(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
更改时触发(尝试在之前的 useEffect
中调用 setDetailIndex
):
useEffect(() =>
// ... effect code
}, [detailIndex]);
useEffect
会在每次渲染时执行,但您可以使用函数中的第二个参数来定义效果何时再次执行。这意味着函数 always 在挂载时执行。在您的情况下,您的第二个 useEffect
将在开始和 detailIndex
更改时为 运行。
更多信息:https://reactjs.org/docs/hooks-effect.html
来源:
Experienced JavaScript developers might notice that the function passed to useEffect is going to be different on every render. [...] You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect: [...]
在我的例子中,即使我在 useEffect() 中使用了第二个参数并且我正在打印参数以确保它没有改变并且它没有改变,组件仍然在更新。问题是我正在使用 map() 渲染组件,并且在某些情况下键发生了变化,如果键发生了变化,对于反应来说它是一个完全不同的对象。
你可以添加第二个守卫,检查 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)
,效果将不会定期触发。
确保代码仅在 "real" 状态更新上运行并且 not 也在第一次渲染上运行的一种方法是分配一个最初是 false
的变量,在第一次渲染 之后只变成 true
,这个变量自然应该是 hook,因此在组件的整个生命周期中它将 memo-ed。
3个不同的比较useEffect
:
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>