从消费者 componentDidMount 更新反应上下文会导致无限重新渲染

Posted

技术标签:

【中文标题】从消费者 componentDidMount 更新反应上下文会导致无限重新渲染【英文标题】:Updating react context from consumer componentDidMount causes infinite re-renders 【发布时间】: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 的重新渲染,但是为什么呢?我哪里出错了,我该如何解决?

【问题讨论】:

【参考方案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 也会很有帮助,以便任何愿意提供帮助的人都可以通过沙箱调试问题。 关于沙盒的好主意,我会构建它并在帖子中编辑指向它的链接。也许它也有助于找到问题:)

以上是关于从消费者 componentDidMount 更新反应上下文会导致无限重新渲染的主要内容,如果未能解决你的问题,请参考以下文章

“componentDidMount”生命周期方法不更新状态

如何更新 componentDidMount 中的上下文状态?

ComponentDidMount 中调用的 setState 没有更新状态? [复制]

React componentDidMount() 初始化状态

组件更新道具时单击DOM元素

使用 Redux 无法在 componentDidMount 中接收异步响应