[英]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.