如何从无限重新渲染中停止反应组件

Posted

技术标签:

【中文标题】如何从无限重新渲染中停止反应组件【英文标题】:How to stop react component from infinite re-render 【发布时间】:2019-11-30 15:48:13 【问题描述】:

我想在我的应用程序中使用 js-cookie,但是当我获得 cookie 时,我的组件会不断重新渲染,直到浏览器崩溃。

我得到的错误是:setState(...): Cannot update during an existing state transition ...

我刚刚使用了 shouldComponentUpdate 但它导致点击事件不起作用。

shouldComponentUpdate(nextProps, nextState) 

    return nextState.language != this.state.language;

有没有人知道除了 shouldComponentUpdate 之外的任何其他解决方案来阻止组件无限重新渲染?

class MainLayout extends Component 
  constructor(props) 
    super(props);
    console.log('constructor');
    this.state = 
      sideBarOpen: false,
      languages: getStoreLanguages,
      language: Cookies.get('langCode')
    
  

  componentWillMount() 
    this.props.langCode();
    this.props.defaultLangCode();
  

  componentDidMount() 
    $('.dropdown-toggle').megaMenu && $('.dropdown-toggle').megaMenu( container: '.mmd' );
  

  shouldComponentUpdate(nextProps, nextState) 
    return nextState.language != this.state.language;
  

  toggleSidebar = () => 
    this.setState(
      sideBarOpen: !this.state.sideBarOpen,
    );
  

  overlayClickHandler = () => 
    this.setState(
      sideBarOpen: false,
    );
  

  handleLanguage = (langCode) => 
    if (Cookies.get('langCode')) 
      return Cookies.get('langCode');
     else 
      Cookies.set('langCode', langCode,  expires: 7 );
      return langCode;
    
  

  render() 
    let overlay =  display: this.state.sideBarOpen ? 'block' : 'none' ;
    const langCode = this.handleLanguage(this.props.params.id);
    const isDefaultLang = isDefaultLanguage(langCode);
    const isValidLang = isValidLanguage(langCode);

    if (langCode && !isValidLang) 
      this.props.router.push(`/$langCode/error`);
    

    if (langCode && isValidLang) 
      const path = getLocalisedPath(langCode, isDefaultLang)
      this.props.router.push("/" + path);
    

    return (
      <div>
        <Helmet>
          <script type="application/ld+json">structuredData()</script>
        </Helmet>
        <TokenManager>
          (state, methods) => (
            <div>
              <WithHelmet ...this.props />
              <WithUserHeaderInfo ...this.props />
              <WithStoreDetail />
              <WithDataLayerStoreDetail />
              <header className='header__nav'>
                <NavbarManagerWillHideOnEditor
                  sideBarOpen=this.state.sideBarOpen
                  toggleSidebar=this.toggleSidebar
                  languages=this.state.languages
                  ...this.props
                />
                <div
                  className="mmm__overlay"
                  onClick=this.overlayClickHandler
                  style=overlay
                />
                <div
                  className="mmm__overlay--hidenav"
                  onClick=this.overlayClickHandler
                  style=overlay
                />
              </header>
              <main>this.props.children</main>
              <Modal modalId="modal-account" size="md">
                (closeModal) => (
                  <Auth
                    closeModal=closeModal />
                )

              </Modal>
              !this.props.location.pathname.startsWith('/checkout') && <FooterWillHideOnEditor languages=this.state.languages/>
            </div>
          )
          
        </TokenManager>
      </div>
    );
  


const mapDispatchToProps = (dispatch, value) => 
  const langCode = Cookies.get('langCode') || value.params.id;
  const defaultLang = getDefaultLanguage();
  const isDefault = isDefaultLanguage(langCode);
  const isValid = isValidLanguage(langCode);
  const lang = !isValid || isDefault ? defaultLang : langCode;

  return 
    langCode: () => dispatch( type: 'SET_LANGUAGE', payload: lang ),
    defaultLangCode: () => dispatch( type: 'SET_DEFAULT_LANGUAGE', payload: defaultLang )
  


export default connect(null, mapDispatchToProps)(MainLayout);

【问题讨论】:

你需要展示你如何更新状态以及为什么......我们可以为它找出一个替代解决方案。没有它,我们无法通过提供的代码找出问题所在..! @Armin : 你能在这里写下你的组件的一般结构吗? @Panther 我已经用代码更新了帖子。谢谢 @sanji_mika 我在这里写了代码谢谢 @ArminSaeedi:我有一个问题:哪个原因使您的组件无限重新渲染?我看到您将 setState 用于“sideBarOpen”,但没有用于状态“语言”,您将 setState 用于“语言”以及导致无限重新渲染的原因? 【参考方案1】:
let overlay =  display: this.state.sideBarOpen ? 'block' : 'none' ;
const langCode = this.handleLanguage(this.props.params.id);
const isDefaultLang = isDefaultLanguage(langCode);
const isValidLang = isValidLanguage(langCode);

if (langCode && !isValidLang) 
  this.props.router.push(`/$langCode/error`);


if (langCode && isValidLang) 
  const path = getLocalisedPath(langCode, isDefaultLang)
  this.props.router.push("/" + path);

这里的代码正在重置 state/ 或 mapDispatchToProps 再次调度。这就是它重新渲染的原因。

【讨论】:

【参考方案2】:

我找到了组件不断重新渲染的原因,实际上是因为:

if (langCode && isValidLang) 
    const path = getLocalisedPath(langCode, isDefaultLang)
    this.props.router.push("/" + path);

始终为真,获取路径并推送到导致组件重新渲染的路由。

谢谢

【讨论】:

恭喜!我说你找到导致重新渲染异常的原因,这对于像这样解决这个问题非常重要。之后,你必须首先找到主要原因(例如通过console.log()),而不是在shouldComponentUpdate中阻塞重新渲染并且不了解问题。

以上是关于如何从无限重新渲染中停止反应组件的主要内容,如果未能解决你的问题,请参考以下文章

避免在反应组件中无限重新渲染

停止重新渲染反应功能组件(使用钩子)

在 history.push 反应之后如何停止重新加载整个组件

如何在反应原生渲染中以无限循环返回数组项视图?

React:如何停止重新渲染组件或对同一路由更改路由器进行 API 调用

React 功能组件在重新挂载后停止渲染状态更改