我可以在useEffect挂钩中设置状态


123

可以说我有一些状态依赖于其他状态(例如,当A更改时,我希望B更改)。

创建一个观察A并将B设置在useEffect钩内的钩子是否合适?

效果是否会级联,从而在我单击按钮时会触发下一个效果,从而导致b发生变化,从而导致第二个效果在下一次渲染之前触发?这样构造代码是否有性能下降?

let MyComponent = props => {
  let [a, setA] = useState(1)
  let [b, setB] = useState(2)
  useEffect(
    () => {
      if (/*some stuff is true*/) {
        setB(3)
      }
    },
    [a],
  )
  useEffect(
    () => {
      // do some stuff
    },
    [b],
  )

  return (
    <button
      onClick={() => {
        setA(5)
      }}
    >
      click me
    </button>
  )
}

Answers:


31

效果始终在渲染阶段完成后执行,即使您将setState放在一个效果中,另一个效果也将读取更新的状态并仅在渲染阶段之后对其执行操作。

话虽如此,最好以相同的效果采取两种行动,除非有可能b由于其他原因而改变的可能性,而不是changing a在这种情况下,您也希望执行相同的逻辑


4
因此,如果A更改B,则组件将渲染两次,对吗?
Dan Ruswick '18

2
我也想知道上述问题的答案
alaboudi

2
@alaboudi是的,如果A发生更改导致运行B的useeffect运行,则该组件确实渲染了两次
Shubham Khatri

@alaboudi是的..如Shubham Khatri所说,它将再次渲染。但是您可以在使用第二个参数重新渲染后跳过调用效果的操作,请参阅reactjs.org/docs/…–
Karthikeyan

107

一般来说,使用setStateinsideuseEffect将创建一个无限循环,很可能是您不想引起的。该规则有几个例外,我将在以后讨论。

useEffect在每次渲染后调用,并在其中setState进行使用时,它将导致组件重新渲染,从而进行调用useEffect,依此类推。

使用useStateinsideuseEffect不会引起无限循环的一种常见情况是,当您将一个空数组作为第二个参数传递给useEffectlike时useEffect(() => {....}, []),这意味着效果函数应该被调用一次:仅在第一次安装/渲染之后。当您在组件中进行数据提取并且希望将请求数据保存在组件的状态时,此方法被广泛使用。


3
setState内部使用的另一种情况useEffectsetting state内部订阅或事件侦听器。但不要忘记取消订阅 reactjs.org/docs/hooks-effect.html#effects-with-cleanup
timqian

35
严格来说,这不是正确的-useState仅在更新状态所使用的值与前一个值不同时才触发,因此除非值在两次循环之间更改,否则将阻止无限循环。
RobM

14
这个答案是不正确的,不是问题所在:在代码中,如果单击按钮,组件仅呈现两次,就没有无限循环
Bogdan D

15
在useEffect中设置状态是一种非常常见的用例。考虑数据加载,useEffect调用API,获取数据,使用useState的set部分进行设置。
badbod99

3
我更新了答案以解决您提到的问题,现在更好吗?
Hossam Mourad

58

为了将来的目的,这可能也有帮助:

可以使用setState来实现useEffect,只需要像已经描述的那样注意而不创建循环。

但这不是唯一可能发生的问题。见下文:

想象一下,您有一个从父级Comp接收的组件,props并且根据props您要设置其Comp状态的更改。由于某些原因,您需要为每个道具进行不同的更改useEffect

不要这样做

useEffect(() => {
  setState({ ...state, a: props.a });
}, [props.a]);

useEffect(() => {
  setState({ ...state, b: props.b });
}, [props.b]);

在此示例中,它可能永远不会更改a的状态:https : //codesandbox.io/s/confident-lederberg-dtx7w

在此示例中发生这种情况的原因是,当您同时更改两个useEffects时,它们都在相同的反应周期中运行prop.aprop.b因此,{...state}当您这样做时,它们的值setState完全相同,useEffect因为它们都在相同的上下文中。当您运行第二个时setState,它将替换第一个setState

这样做吧

解决这个问题的方法基本上setState是这样的:

useEffect(() => {
  setState(state => ({ ...state, a: props.a }));
}, [props.a]);

useEffect(() => {
  setState(state => ({ ...state, b: props.b }));
}, [props.b]);

在此处检查解决方案:https : //codesandbox.io/s/mutable-surf-nynlx

现在,当您继续执行时,您始终会收到状态的最新和正确值setState

我希望这可以帮助别人!


3
上述解决方案对我setName(name => ({ ...name, a: props.a }));
有所

2
这也对我的箭头功能有所帮助setItems(items => [...items, item])
Stefan Zhelyazkov

哇,真棒。您今天保存了我的*。
Pratik Soni

在没有解决方案的情况下,从上午7点花费到下午3点,现在您救了我。
Dinindu Kanchana

24

useEffect可以钩在某个道具或状态上。因此,您需要做的是避免无限循环挂钩的事情是将某些变量或状态绑定到效果上

例如:

useEffect(myeffectCallback, [])

仅当组件渲染后,上述效果才会触发。这类似于componentDidMount生命周期

const [something, setSomething] = withState(0)
const [myState, setMyState] = withState(0)
useEffect(() => {
  setSomething(0)
}, myState)

上面的效果只会触发我已更改的componentDidUpdate状态,这与之类似,只是并非每个更改的状态都会触发它。

您可以通过此链接阅读更多详细信息


1
谢谢,此答案以其他答案未解决的方式解决了useEffect的依赖项数组。将空数组用作useEffect的第二个参数将确保useEffect在组件渲染后立即执行,但是包含具有特定状态或特定状态的数组将导致useEffect在引用中的状态更改时执行。
adriaanbd

11

▶1.我可以在useEffect挂钩中设置状态吗?

原则上,你可以自由设定的状态,你需要它-包括内部useEffect即使在渲染。只需通过deps正确设置Hook和/或有条件地设置状态来确保避免无限循环。


▶2.假设我有一些状态取决于其他状态。创建一个观察A并将B设置在useEffect钩内的钩子是否合适?

您刚刚描述了以下的经典用例useReducer

useReducer通常,useState当您具有包含多个子值的复杂状态逻辑或下一个状态取决于上一个时,通常更可取。(React文档

当设置状态变量取决于另一个状态变量的当前值时,您可能想要尝试将它们都替换为useReducer。[...]当您发现自己正在写作时 setSomething(something => ...),是考虑使用reducer的好时机。(Dan Abramov,反应过度的博客


▶3.效果是否会级联,以便在单击按钮时会先触发第一个效果,从而导致b改变,从而导致第二个效果在下一次渲染之前触发?

useEffect始终提交渲染并应用DOM更改运行。第一个效果会触发,更改b并引起重新渲染。渲染完成后,由于b更改,将运行第二个效果。


▶4.这样的代码结构在性能方面有不利之处吗?

是。通过将的状态更改包装b在单独的useEffecta,浏览器具有附加的布局/绘制阶段-用户可能会看到这些效果。如果您不想useReducer尝试,可以直接更改b状态a

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.