我可以在 useEffect 钩子内设置状态吗

IT技术 javascript reactjs react-hooks
2021-02-08 23:42:07

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

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

效果是否会级联,当我单击按钮时,第一个效果将触发,导致 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>
  )
}
6个回答

一般来说,使用setStateinsideuseEffect会创建一个你很可能不想引起的无限循环。该规则有几个例外,我稍后会介绍。

useEffect在每次渲染后调用,当setState在其中使用时,它将导致组件重新渲染,这将调用useEffect等等。

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

setState内部使用的另一种情况useEffectsetting state内部订阅或事件侦听器。但是不要忘记取消订阅 reactjs.org/docs/hooks-effect.html#effects-with-cleanup
2021-03-19 23:42:07
我更新了答案以解决您提到的问题,现在更好了吗?
2021-03-30 23:42:07
在 useEffect 中设置状态是一个非常常见的用例。考虑数据加载,useEffect 调用API,获取数据,使用useState 的set 部分进行设置。
2021-04-02 23:42:07
这并非严格正确 - useState 仅在您更新状态的值与前一个值不同时才会触发,因此除非值在循环之间发生变化,否则将防止无限循环。
2021-04-06 23:42:07
这个答案是不正确的,不是问题的重点:在代码中,当单击按钮时组件只呈现两次,没有无限循环
2021-04-10 23:42:07

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

可以使用 setState 在useEffect你只需要注意已经描述的不要创建循环。

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

想象一下,您有一个组件从父组件Comp接收props并根据props您想要设置Comp的状态更改出于某种原因,您需要为每个props更改不同的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 在同一个react周期中运行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.

我希望这可以帮助别人!

我试过这个解决方案。setState(state => ({ ...state, useEffectValue }));当我登录时state,它仍然是空的。如果我传入useEffectValue输出数组,我会得到一个无限循环。¯_(ツ)_/¯
2021-03-16 23:42:07
这在箭头函数部分也对我有帮助 setItems(items => [...items, item])
2021-03-17 23:42:07
对这个令人难以置信的小费表示感谢。我确实覆盖了多个 useEffect 调用的状态,这些调用在安装组件时触发了所有调用。
2021-04-04 23:42:07
以上解决方案帮助了我 setName(name => ({ ...name, a: props.a }));
2021-04-08 23:42:07
从早上 7 点到下午 3 点都没有解决方案,现在你救了我。
2021-04-09 23:42:07

效果总是在渲染阶段完成后执行,即使您在一个效果中设置了状态,另一个效果将读取更新的状态并仅在渲染阶段之后对其采取行动。

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

所以如果 A 改变 B,组件会渲染两次,对吗?
2021-03-19 23:42:07
我也想知道上面问题的答案
2021-03-19 23:42:07
@alaboudi 是的……正如 Shubham Khatri 所说,它将再次渲染。但是您可以在使用第二个参数重新渲染后跳过调用您的效果,请参阅reactjs.org/docs/...
2021-04-11 23:42:07
@alaboudi 是的,如果 A 更改导致 useEffect 运行设置 B,则该组件会渲染两次
2021-04-12 23:42:07

useEffect可以挂钩某个props或状态。所以,你需要做的事情来避免无限循环钩子是绑定一些变量或状态来影响

例如:

useEffect(myeffectCallback, [])

上述效果只会在组件渲染后触发。这类似于componentDidMount生命周期

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

上面的效果只会触发我的状态改变了这类似于componentDidUpdate除了不是每个改变状态都会触发它。

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

谢谢,这个答案以其他答案没有的方式解决了 useEffect 的依赖数组。包含一个空数组作为 useEffect 的第二个参数将确保 useEffect 在组件渲染后执行,但包含具有特定状态或特定状态的数组将导致 useEffect 在引用中的状态发生变化时执行。
2021-03-14 23:42:07
不明白withState()代表什么我在文档中找不到任何对它的引用。
2021-04-10 23:42:07

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

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


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

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

useReduceruseState当您具有涉及多个子值的复杂状态逻辑或下一个状态取决于前一个状态时,通常更可取react文档

当设置状态变量取决于另一个状态变量的当前值时,您可能想尝试将它们都替换为useReducer. [...] 当您发现自己在编写 时 setSomething(something => ...),现在是考虑使用 reducer 的好时机。丹·阿布拉莫夫,react过度的博客


▶ 3. 效果是否会级联,以至于当我单击按钮时,第一个效果将触发,导致 b 更改,导致第二个效果在下一次渲染之前触发?

useEffect始终提交渲染并应用 DOM 更改运行第一个效果触发、更改b并导致重新渲染。此渲染完成后,第二个效果将因b更改而运行


▶ 4. 像这样构造代码是否有任何性能上的缺点?

是的。通过将 的状态变化包装b在单独的useEffectfor 中a,浏览器有一个额外的布局/绘制阶段 - 这些效果对用户来说可能是可见的。如果实在useReducer想不通,可以直接b用下面的方法改变状态a