当前位置: 代码迷 >> JavaScript >> 我为什么会遇到永久违规:永久违规:超出最大更新深度。 在setState上?
  详细解决方案

我为什么会遇到永久违规:永久违规:超出最大更新深度。 在setState上?

热度:99   发布时间:2023-06-08 09:24:41.0

我有这个组成部分:

// 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以便可以切换组件的可见性。

如上面的注释所述,您不能直接在渲染函数内部调用setState

我看不出有任何理由将selected值保持在您的状态,因为它仅取决于其中已存在的另一个值,此信息是多余的。

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

{routes[i].selected &&

到以下内容:

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

要产生相同的结果。

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

由于React将以批处理状态更新的方式,如果您需要根据先前状态设置状态,则应在回调中进行所有解构:

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

另外,正如@Adeel提到的,您不应从render执行上下文内部调用this.setState 移动该调用componentDidUpdate代替。

render调用setState是无限rerender loop的实际原因,因为设置state调用rerender,后者会调用另一个setState ,后者又调用另一个rerender等。

因此,我建议将setState部分移到componentDidUpdate并在那里进行控制。 这是我的意思的 ,几乎没有任何变化来描述这一点。

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)}
      </>
    );
  }
}