反应-替代功能组件中的`setState`回调?

时间:2020-02-15 09:41:55

标签: javascript reactjs react-hooks

我们已迁移到“反应功能组件”,而不是“基于类的组件”。我找不到setState callback function的替代逻辑。即,我有一个带有状态的功能组件,并且我想创建一个事件处理程序函数,该函数按顺序多次更改状态,但需要注意的是,我不知道状态的当前值(可能是true / false)。以下示例可能更有意义。

const Example = () => {
  const [ openDoor, setOpenDoor ] = useState(false); 
  // the following handler should swich 'openDoor' state to inverse of
  // current state value. Then after setTimeout duration, inverse it again
  const toggleOpenDoor = () => {
  setOpenDoor(!openDoor);
  // within setTimeout below, '!openDoor' does not work because it still
  // receives the same value as above because of async nature of 
  // state updates
  setTimeout(() => setOpenDoor(!openDoor), 500)
  }
  return(...);
}

在基于class的组件中,我们具有回调参数,该参数将在上一次更新后更新状态。如何使用状态挂钩在上述功能组件中实现相同的目的?

6 个答案:

答案 0 :(得分:2)

您可以使用useEffect挂钩查看状态更改的发生时间。

useEffect(() => { 
  // do something
  console.log('openDoor change', openDoor)
}, [openDoor]);

答案 1 :(得分:2)

我想知道useEffect是否是最佳解决方案。特别是在useEffect中调用setTimeout会导致无限循环,因为每次我们调用setOpenDoor时,应用都会呈现,然后调用useEffect再次调用setTimeOut,该方法将调用setOpenDoor函数...

setTimeout -> setOpenDoor -> useEffect -> setTimeout -> ... hell  

当然,您可以使用if语句wihin useEffect,其使用方式与@ksav建议的方式相同,但不能满足@Kayote的一项要求:

我不知道state的当前值(可能为true / false)

这是一种无需useEffect即可完成上述要求的解决方案:

Code working in codesandbox

在那里,看到这段代码的重要性:

const toggleOpenDoor = () => {
  setOpenDoor(!openDoor);
  setTimeout(() => setOpenDoor(openDoor => !openDoor), 500);
};

由于我们正在使用setTimeout,因此需要将回调传递给setOpenDoor而不是更新状态。这是因为我们要发送“当前”状态。如果改为发送新状态,则在setTimeOut处理该状态时,它将已经更改(因为在setTimeOut使用setOpenDoor(!openDoor);执行其回调之前,我们已进行了更改),并且不会进行任何更改。

答案 2 :(得分:1)

我将告诉您,它的工作方式与this.setState几乎相同,只是传递了一个回调函数,该函数以以前的状态为参数并返回新状态(docs)< / p>

const Example = () => {
  const [openDoor, setOpenDoor] = useState(false); 

  const toggleOpenDoor = () => {
    setOpenDoor(!openDoor);
    setTimeout(() => setOpenDoor(prevDoor => !prevDoor), 500)
  }
  return(...);
}

为了让您知道它何时发生更改,您可以使用useEffect回调,每次依赖项数组(docs)发生变化时都会调用该回调

const Example = () => {
  const [openDoor, setOpenDoor] = useState(false); 

  useEffect(() => {
    console.log('openDoor changed!', openDoor)
  }, [openDoor])

  const toggleOpenDoor = () => {
    setOpenDoor(!openDoor);
    setTimeout(() => setOpenDoor(prevDoor => !prevDoor), 500)
  }
  return(...);
}

:)

答案 3 :(得分:1)

您可以使用useEffect钩子来实现此目的。

setOpenDoor(!openDoor);

useEffect(() => {
   // Here your next setState function
}, [openDoor]);

有关钩子的更多信息,请查看https://reactjs.org/docs/hooks-effect.html

答案 4 :(得分:1)

您应该只在setTimeout回调中使用useEffect

const App = () => {
  const [openDoor, setOpenDoor] = useState(false);

  const toggle = () => setOpenDoor(prevOpen => !prevOpen);

  useEffect(() => {
    const id = setTimeout(() => toggle(), 1000);
    return () => clearTimeout(id);
  }, [openDoor]);

  return <Container>isOpen: {String(openDoor)}</Container>;
};

Edit kind-dirac-b68xf

答案 5 :(得分:1)

import React, { useState, useEffect } from "react";

const Example = () => {
  const [openDoor, setOpenDoor] = useState(false);
  const toggleOpenDoor = () => {
    setOpenDoor(!openDoor);
  };
  useEffect(() => {
    console.log(openDoor);
    if (openDoor) {
      setTimeout(() => setOpenDoor(!openDoor), 1500);
    }
  }, [openDoor]);
  return (
    <>
      <button onClick={toggleOpenDoor}>Toggle</button>
      <p>{`openDoor: ${openDoor}`}</p>
    </>
  );
};

export default Example;

Codesandbox