【问题标题】:Why am I getting Invariant Violation: Invariant Violation: Maximum update depth exceeded. on setState?为什么我得到不变违规:不变违规:超过最大更新深度。在 setState 上?
【发布时间】:2019-07-06 12:42:49
【问题描述】:

我有这个组件:

// imports

export default class TabViewExample extends Component {
  state = {
    index: 0,
    routes: [
      { key: 'first', title: 'Drop-Off', selected: true },
      { key: 'second', title: 'Pick up', selected: false },
    ],
  };

  handleIndexChange = index => this.setState({ index });

  handleStateIndexChange = () => { // FUNCTION WITH THE ERROR
    const { index } = this.state;
    this.setState(({ routes }) => ({
      routes: routes.map((route, idx) => ({
        ...route,
        selected: idx === index,
      })),
    }));
  };

  renderTabBar = props => {
    const { routes } = this.state;
    this.handleStateIndexChange(); // HERE I GET THE ERROR
    return (
      <View style={tabViewStyles.tabBar}>
        {props.navigationState.routes.map((route, i) => {
          return (
            <>
              <TouchableOpacity
                key={route.key}
                style={[
                  tabViewStyles[`tabStyle_${i}`],
                ]}
                onPress={() => this.setState({ index: i })}
              >
                <Text>
                  {route.title}
                </Text>
                // THE FUNCTION ATTEMPTS TO SHOW AN ELEMENT WHEN
                // THE INDEX OF A ROUTE IS selected true
                {routes[i].selected && (
                  <View
                    style={{
                      flex: 1,
                    }}
                  >
                    <View
                      style={{
                        transform: [{ rotateZ: '45deg' }],
                      }}
                    />
                  </View>
                )}
              </TouchableOpacity>
            </>
          );
        })}
      </View>
    );
  };

  renderScene = SceneMap({
    first: this.props.FirstRoute,
    second: this.props.SecondRoute,
  });

  render() {
    return (
      <TabView
        navigationState={this.state}
        renderScene={this.renderScene}
        renderTabBar={this.renderTabBar}
        onIndexChange={this.handleIndexChange}
      />
    );
  }
}

完全错误:

不变违规:不变违规:超过最大更新深度。当组件在 componentWillUpdate 或 componentDidUpdate 中重复调用 setState 时,可能会发生这种情况。 React 限制了嵌套更新的数量以防止无限循环。

这是给出错误的函数:

  handleStateIndexChange = () => { // FUNCTION WITH THE ERROR
    const { index } = this.state;
    this.setState(({ routes }) => ({
      routes: routes.map((route, idx) => ({
        ...route,
        selected: idx === index,
      })),
    }));
  };

我只需将selected 的状态设置为true,这样我就可以切换组件的可见性。

【问题讨论】:

  • 您永远不会在此调用中直接在渲染中设置状态,方法是在渲染中调用执行该操作的函数。
  • @AdeelImran 我想给它打电话吗?
  • setState in render 触发另一个 render 触发另一个 setState 在循环中。这是一种反模式。尝试在render 之前将其提升为生命周期方法。
  • @KarenGrigoryan 如果你看到我的代码,我有这个this.handleStateIndexChange(); // HERE I GET THE ERROR,这是我在renderTabBar 内部调用的函数,它调用了渲染方法。另外,如果我在componentDidMount 上使用setState,我会收到错误消息,即我不应该在componentDidMount 函数上使用setState
  • 对不起,我的意思是 componentDidUpdate 不是 componentDidMount

标签: javascript reactjs ecmascript-6


【解决方案1】:

如上面评论中所述,您不能直接在渲染函数中调用setState

我认为没有任何理由将 selected 值保留在您的状态中,因为它仅依赖于其中已有的另一个值,此信息是多余的。

您应该从routes 中的所有对象中删除selected 并更改您的JSX 条件:

{routes[i].selected &&

致以下:

{i === this.state.index &&

产生相同的结果。

您现在可以从代码中删除 handleStateIndexChange

【讨论】:

    【解决方案2】:

    由于 React 会批量更新状态,如果你需要根据之前的状态设置状态,你应该在回调中做所有的解构:

      handleStateIndexChange = () => {
        this.setState(({ index, routes }) => ({
          routes: routes.map((route, idx) => ({
            ...route,
            selected: idx === index,
          })),
        }));
      };
    

    另外,正如@Adeel 所提到的,您不应该从render 执行上下文中调用this.setState。将那个电话改为componentDidUpdate

    【讨论】:

    • 根据@Adeel Imran 的评论,我不能在我正在做的渲染方法上调用 setState。如果你看到我的代码,我有这个this.handleStateIndexChange(); // HERE I GET THE ERROR,这是我在renderTabBar 中调用的函数,它是对渲染方法的调用。另外,如果我在componentDidMount 上使用setState,我会收到错误消息,即我不应该在componentDidMount 函数上使用setState
    • 是的 @Adeel 是正确的,在 render 中调用 setState 会导致无限循环。您应该使用componentDidUpdate 来执行状态更新。
    • 对不起,我的意思是 componentDidUpdate 不是 componentDidMount
    【解决方案3】:

    render中调用setState是无限重新渲染loop的实际原因, 因为设置state 调用重新渲染,它调用另一个setState,它调用另一个重新渲染等等。

    所以我建议将setState 部分移动到componentDidUpdate 并在那里控制它。这是approximation 的意思,我对这一点的描述几乎没有变化。

    class App extends React.Component {
      state = {
        index: 0,
        routes: [
          {
            key: "first",
            title: "Drop-Off",
            selected: true
          },
          {
            key: "second",
            title: "Pick up",
            selected: false
          }
        ]
      };
    
      componentDidUpdate(_, prevState) {
        if (prevState.index !== this.state.index) {
          this.handleStateIndexChange();
        }
      }
    
      handleIndexChange = () => {
        const randomIndexBetweenZeroAndOne = (Math.random() * 100) % 2 | 0;
    
        this.setState({
          index: randomIndexBetweenZeroAndOne
        });
      };
    
      getMappedRoutes = (routes, index) =>
        routes.map((route, idx) => ({
          ...route,
          selected: idx === index
        }));
    
      handleStateIndexChange = () => {
        this.setState(({ routes, index }) => ({
          routes: this.getMappedRoutes(routes, index)
        }));
      };
    
      render() {
        const { routes } = this.state;
    
        return (
          <>
            <button onClick={this.handleIndexChange}>Change Index</button>
            {this.state.routes.map(JSON.stringify)}
          </>
        );
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-08-11
      • 2019-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-14
      • 2019-08-29
      • 2020-05-06
      相关资源
      最近更新 更多