【问题标题】:React usEffect firing when it shouldn't be在不应该触发的时候响应 useEeffect 触发
【发布时间】:2020-09-25 01:52:55
【问题描述】:

我有一个带有 props 的 react 组件,它由 redux 连接方法传递。有一个 useEffect 专门链接到该道具,它应该在它更改时执行异步调用。问题是每当我在该应用程序中的任何其他位置更改 redux 状态时,useEffect 都会触发,尽管我将 useEffect 附加到不改变的道具。

useEffect 方法是这样的

  useEffect(() => {
    if (userPhoneNumber) {
      myAsyncFunction()
        .then(() => {
          showData()
        })
    }
  }, [userPhoneNumber])

userPhoneNumber 属性通过 react-redux connect 方法传递,如下所示:

const mapStateToProps = state => {
  return {
    userPhoneNumber: state.appState.userPhoneNumber
  }
}

export default connect(mapStateToProps)(MyComponent)

据我了解,这可能会在两个潜在的地方发生故障。一方面,如果 userPhoneNumber 道具没有改变,则不应触发 useEffect 。此外,mapStateToProps 方法没有返回新值,因此它不应该触发任何类型的重新渲染。

导致意外 useEffect 调用的 redux 状态更改来自同级组件,这些组件具有与该组件类似的 react-redux 连接设置,具有不同的 state 到 prop 映射。

【问题讨论】:

  • userPhoneNumber 是字符串还是对象?
  • 当 reducer 运行时,您将拥有一个全新的状态对象,因此是一个新的 userPhoneNumber 属性引用。你应该记住userPhoneNumber 的值。我使用reselect 来创建记忆状态选择器,但您可能可以使用useMemo 钩子并在依赖项中使用 that 记忆值以获得效果。
  • @DrewReese 尽管 redux 为每个对象创建了一个新的 state 对象,但它只是一个浅拷贝,只有被 reducer 更改的节点是一个新对象。所有其他节点将是同一个对象。所以除非调度动作没有改变userPhoneNumber oldState.userPhoneNumber === newState.userPhoneNumber
  • @SubinSebastian mapStateToProps 正在返回一个新对象。 github.com/reduxjs/reselect#motivation-for-memoized-selectors
  • Also, the mapStateToProps method is not returning a new value so it should not be triggering any sort of a rerender 如果父组件重新渲染,每个 React 组件都会重新渲染,除非它是 Pure Component。要制作一个纯函数组件,你必须用React.memo包装它

标签: reactjs redux react-redux use-effect


【解决方案1】:

当 reducer 运行时,您将拥有一个全新的状态对象,因此是一个新的 userPhoneNumber 属性引用。您应该记住 userPhoneNumber 的值。我使用reselect 来创建记忆状态选择器,但您可能可以使用useMemo 钩子并在依赖项中使用 that 记忆值以获得效果。

const memoizedUserPhoneNumber = useMemo(() => userPhoneNumber, [userPhoneNumber]);

useEffect(() => {
  if (memoizedUserPhoneNumber) {
    myAsyncFunction()
      .then(() => {
        showData()
      })
  }
}, [memoizedUserPhoneNumber]);

使用重新选择

import { createSelector } from 'reselect';

const appState = state => state.appState || {};

const userPhoneNumber = createSelector(
  appState,
  appState => appState.userPhoneNumber;
);

...

const mapStateToProps = state => {
  return {
    userPhoneNumber: userPhoneNumber(state),
  }
}

这些都无助于防止整个功能组件在父级重新渲染时重新渲染,因此为了解决这个问题,您需要帮助“记忆”使用 memo 输入组件的道具HOC。

export default memo(connect(mapStateToProps)(MyComponent))

【讨论】:

  • 我尝试了这两个示例,但不幸的是同样的问题仍然存在。
  • 您是否进行了任何日志记录以确保 userPhoneNumber 实际上没有在状态中发生突变或更新?
  • 是的,每次调用useEffect都是同一个字符串。即使我将[] 作为第二个参数传递给useEffect,这应该使它只触发初始加载,但每次redux 状态更改时都会触发
  • 是字符串,不能变异,只能按值比较
猜你喜欢
  • 2019-02-13
  • 2017-09-20
  • 2021-05-09
  • 1970-01-01
  • 1970-01-01
  • 2010-11-18
  • 1970-01-01
  • 1970-01-01
  • 2015-08-03
相关资源
最近更新 更多