【问题标题】:Updating react context from consumer componentDidMount causes infinite re-renders从消费者 componentDidMount 更新反应上下文会导致无限重新渲染
【发布时间】:2019-06-10 15:58:17
【问题描述】:

我正在尝试使用 Context API 在 React 中进行一些状态管理;我想要实现的是,当我到达特定路线时,我从服务器加载数据,将其存储在上下文中,并将其显示在页面本身中。这导致了一个无限循环,其中对服务器的请求一遍又一遍地完成(并且永远不会停止)。

我正在尝试为提供者和消费者逻辑使用更高阶的组件:

import React, { Component, createContext } from 'react';

import RequestStatus from '../RequestStatus';
import { getData } from '../Api';

const dataCtx = createContext({
  data: [],
  getData: () => {},
  requestStatus: RequestStatus.INACTIVE,
});
export default dataCtx;

export function dataContextProvider(WrappedComponent) {
  return class extends Component {
    constructor(props) {
      super(props);

      this.state = {
        data: [],
        getData: this.getData.bind(this),
        requestStatus: RequestStatus.INACTIVE,
      };
    }

    async getData() {
      this.setState({ requestStatus: RequestStatus.RUNNING });
      try {
        const data = await getData();
        this.setState({ data, requestStatus: RequestStatus.INACTIVE });
      } catch (error) {
        this.setState({ requestStatus: RequestStatus.FAILED });
      }
    }

    render() {
      return (
        <dataCtx.Provider value={this.state}>
          <WrappedComponent {...this.props} />
        </dataCtx.Provider>
      );
    }
  };
}

export function dataContextConsumer(WrappedComponent) {
  return function component(props) {
    return (
      <dataCtx.Consumer>
        {dataContext => <WrappedComponent dataCtx={dataContext} {...props} />}
      </dataCtx.Consumer>
    );
  };
}

提供者是 App 组件本身:

import React, { Fragment } from 'react';

import { dataContextProvider } from './contexts/DataContext';
import { userContextProvider } from './contexts/UserContext';

import AppRoutes from './AppRoutes';

function App() {
  return (
    <Fragment>
      <main>
        <AppRoutes />
      </main>
    </Fragment>
  );
}

export default userContextProvider(dataContextProvider(App));

这是导致循环的消费者:

import React, { Component } from 'react';

import RequestStatus from './RequestStatus';
import { dataContextConsumer } from './contexts/DataContext';

class DataList extends Component {
  async componentDidMount() {
    const { dataCtx: { getData } } = this.props;
    await getData();
  }

  render() {
    const { dataCtx: { data, requestStatus } } = this.props;
    return (
      {/* display the data here */}
    );
  }
}

export default dataContextConsumer(DataList);

我已尝试从消费者的 HOC 切换,但没有帮助:

import React, { Component } from 'react';

import RequestStatus from './RequestStatus';
import dataCtx from './contexts/DataContext';

class DataList extends Component {
  async componentDidMount() {
    const { getData } = this.context;
    await getData();
  }

  render() {
    const { data, requestStatus } = this.context;
    return (
      {/* display the data here */}
    );
  }
}

DataList.contextType = dataCtx;

export default DataList;

DataList 只是我想要触发上下文更新的页面之一。

我猜是 Provider 导致了整个 App 的重新渲染,但是为什么呢?我哪里出错了,我该如何解决?

【问题讨论】:

    标签: reactjs react-context


    【解决方案1】:

    好的,在尝试在沙盒中复制问题后,我意识到问题所在:我将父组件包装在渲染函数内的 HOC 中,如下所示:

    <Route exact path="/datapage" component={requireLoggedInUser(Page)} />
    

    这会强制 DataList 组件在每次应用重新渲染时被销毁 + 重新创建。

    【讨论】:

      【解决方案2】:

      请求循环发生是因为DataList 组件被重新渲染,调用ComponentDidMount,在每次渲染后调用getData()

      如果组件的 props 或 state 发生变化,组件就会呈现。

      getData() 设置 state 属性 requestStatus(这就是你的整个应用程序被重新渲染的原因),这是 DataList 的一个属性 - 导致 DataList 的重新渲染。

      你不应该使用requestStatus 作为DataList 的道具,因为无论如何你都是从上下文中得到的。

      【讨论】:

      • 我不确定你的意思,requestStatus 不是DataList 的道具,它就像data 一样是上下文的一部分。此外,根据反应文档,componentDidMount 应该仅在组件添加到 DOM 树时调用,而不是每次渲染时调用。组件是否随时离开 DOM?
      【解决方案3】:

      这可能是因为您的提供程序 (dataContextProvider) 级函数 getData 与您从 ../Api 导入的函数具有相同的命名空间。

      然后我相信当下面的代码行const data = await getData();在下面的代码块中运行时,它实际上调用了提供者getData函数,从而导致了一个循环。

        async getData() {
            this.setState({ requestStatus: RequestStatus.RUNNING });
            try {
              const data = await getData();
              this.setState({ data, requestStatus: RequestStatus.INACTIVE });
            } catch (error) {
              this.setState({ requestStatus: RequestStatus.FAILED });
            }
          }
      

      【讨论】:

      • 我希望是这样,但似乎并非如此。即使名称不同,我仍然会得到循环。
      • 嗯,很公平!我不确定它还能是什么。其他一切似乎都很好。我建议逐步消除一些复杂性,以便您可以缩小导致问题的范围。如果您使用项目的基本代码创建 sandbox 也会很有帮助,以便任何愿意提供帮助的人都可以通过沙箱调试问题。
      • 关于沙盒的好主意,我会构建它并在帖子中编辑指向它的链接。也许它也有助于找到问题:)
      猜你喜欢
      • 2022-10-23
      • 1970-01-01
      • 1970-01-01
      • 2019-07-16
      • 1970-01-01
      • 2021-04-23
      • 2019-07-27
      • 2019-09-30
      • 2021-04-08
      相关资源
      最近更新 更多