繁体   English   中英

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>;

};

为什么会这样?

更新:如果不清楚,我正在尝试在组件更新时运行效果,因为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]);

useEffect 钩子 API 参考

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM