React Hooks-即使状态未更改,也会触发useEffect

时间:2019-02-28 10:48:56

标签: reactjs react-hooks

我在组件内部设置了一个效果,如果另一个状态属性发生变化,该效果也会改变视图。但是由于某种原因,即使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发生了变化。挂载时不行。

6 个答案:

答案 0 :(得分:7)

就我而言,即使我在useEffect()中使用了第二个参数,并且该组件仍在打印,以确保其不变和不变,该组件仍在不断更新。问题是我正在使用map()渲染组件,并且在某些情况下键发生了更改,如果键发生更改,则它是一个完全不同的对象。

答案 1 :(得分:3)

默认情况下,React Hooks中的

useEffect在每个渲染上都执行,但是您可以在函数中使用第二个参数来定义何时再次执行效果。这意味着该功能在安装时始终始终执行。根据您的情况,第二个useEffect将在启动时以及detailIndex更改时运行。

更多信息:https://reactjs.org/docs/hooks-effect.html

来源:

  

经验丰富的JavaScript开发人员可能会注意到,传递给useEffect的函数在每个渲染器上都会有所不同。 [...]如果在重新渲染之间某些值没有更改,您可以告诉React跳过应用效果。为此,请将数组作为第二个可选参数传递给useEffect:[...]

答案 2 :(得分:3)

您可以添加第二个防护,检查detailIndex是否已从初始值更改为-1

 useEffect(
    a => {
     if(detailIndex != -1)
      {  console.log('Running effect', detailIndex);
        // On mount shows "null"!! Should not have run...
        setView('detail');
    }},
    [detailIndex]);

答案 3 :(得分:1)

确保代码仅在“真实” 状态更新上运行并且不能也在首次渲染上运行的一种方法是,分配一个初始为{{1} },并且仅在第一个渲染后变为false ,并且该变量自然应该是hook,因此它将在组件的寿命。

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"))

答案 4 :(得分:0)

useEffect总是在初始渲染后运行。

来自docs

  

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 hook API reference

答案 5 :(得分:0)

并且仅当mode='reschedule'的第二个参数状态通过引用(而不是值)更改时,才会触发效果。

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}),效果将不会定期触发。