【问题标题】:react component renders twice with only one setState in componentWillMountreact 组件在 componentWillMount 中只有一个 setState 渲染两次
【发布时间】:2018-04-04 06:31:11
【问题描述】:

我有这个组件,它的作用是通过 fetch 从服务器获取类别对象,我把它放在 componentWillMount 中。 setState 只有一个:它在componentWillMount promise回调中,当数据从服务器到达时。

问题是组件渲染了两次,第一次 this.state.category 的值是构造函数的初始状态,然后第二次 this.state.category 等于从服务器接收到的对象。 如何在 setState 之后只调用一次 render 方法?

import ReactDom from 'react-dom';
import React from 'react';
import Breadcrumb from './Breadcrumb';


export default class Category extends React.Component{

constructor(props){
    super(props);
    this.state = {
        category: {
            'published_projects':[{'images':[]}],
            'unpublished_projects':[{'images':[]}],
        }
    };

}

componentWillMount(){
    fetch(route('api.categories.show', {'slug':this.props.match.params.slug}))
        .then(response => response.json())
        .then(data => {this.setState({category: data});});
}

totalLikesCount(){
    return this.state.category.published_projects.reduce(( accu, item ) =>
        accu + item.likes_count ,0
    );
}
totalViewsCount(){
    return this.state.category.published_projects.reduce(( accu, item ) =>
        accu + item.views_count ,0
    );
}

totalPicturesCount(){
    return this.state.category.published_projects.reduce(( accu, item ) =>
        accu + item.images.length ,0
    );
}

render(){
    const category = this.state.category;
    console.log(category);
    return (
        <div className="w980px center">

            <div className="CategoryHeader-bg-wrapper margin-rt10 margin-lt10">
                <img alt="" src={category.picture_url}/>
                <div className="CategoryHeader-bg-gradient">
                    <h1>
                        { category.name }
                    </h1>
                </div>
            </div>
            <div className="CategoryHeader-metrics-wrapper u-boxShadow margin-rt10 margin-lt10">
                <ul className="CategoryHeader-metrics-list">
                    <li className="CategoryHeader-metrics-item txt-center">
                        <span className="CategoryHeader-metrics-label">
                            Projets
                        </span>
                        <span className="CategoryHeader-metrics-value">
                            { category.published_projects.length }
                        </span>
                    </li>
                    <li className="CategoryHeader-metrics-item txt-center">
                        <span className="CategoryHeader-metrics-label">
                            Total de mentions j'aime
                        </span>
                        <span className="CategoryHeader-metrics-value">
                            { this.totalLikesCount() }
                        </span>
                    </li>
                    <li className="CategoryHeader-metrics-item txt-center">
                        <span className="CategoryHeader-metrics-label">
                            Total des vues
                        </span>
                        <span className="CategoryHeader-metrics-value">
                            { this.totalViewsCount() }
                        </span>
                    </li>
                    <li className="CategoryHeader-metrics-item txt-center">
                        <span className="CategoryHeader-metrics-label">
                            Total des photos
                        </span>
                        <span className="CategoryHeader-metrics-value">
                            { this.totalPicturesCount() }
                        </span>
                    </li>
                </ul>
            </div>
        </div>
    );
}

}

【问题讨论】:

  • 唯一的方法是在父组件中进行数据调用,并且在数据存在之前不挂载Category 组件。您不能阻止组件的初始渲染。可以查看render方法中是否加载了数据,如果不存在则return null,但还是会调用render函数两次。

标签: javascript reactjs


【解决方案1】:

您的代码可能会间接要求渲染两次:

// This calls to setState (but after a while)
fetch(route('api.categories.show', {'slug':this.props.match.params.slug}))
    .then(response => response.json()) // These are not called yet
    .then(data => {this.setState({category: data});}); 

// Then immediately after calling fetch, it gets to the next stages of the react lifecycles, 
// (like render)

render() ...

// Then setState gets called because it is an asynchronous call,
.then(data => {this.setState({category: data});}); 

// and setState calls to render again. 

问题是组件渲染了两次

你有一个丑陋的,不推荐的选项是return null第一次从render(当没有获取数据时),当你有数据时正常返回,阻止元素被渲染。

另一个选项是捕捉数据是否在其父级中获取,并在那里隐藏元素。

【讨论】:

    【解决方案2】:

    没有办法延迟 渲染 方法调用直到数据返回。

    组件在执行渲染之前不会等待您的异步操作完成。因此,您至少会有 2 个渲染调用:

    • 组件首次挂载时的一个
    • 异步操作完成并执行 setState 回调的第二次

    但是,您可以通过返回 null 在第一次渲染调用期间防止组件被添加到 DOM

    您可以通过向您的状态添加一个加载标志然后在您的数据返回时翻转它来实现这一点......然后将您的渲染方法修改为如下所示:

    render() {
       if (this.state.loading) return (null);
       return (
          <div className="w980px center">
             ...
          </div>
       );
    }
    

    【讨论】:

      【解决方案3】:

      这里的问题可能是fetch 需要一些时间来从服务器加载数据,因此,DOM 会在这段时间内加载,并在带回fetched 数据后更新。

      为了确保组件只加载一次,添加一个新的状态变量,isLoaded 或其他东西,并将其设为 false。然后,一旦你的fetch 完成,将其设置为true,并且仅在isLoaded 为真时渲染组件。

      这是一个代码示例:

      constructor(props){
          super(props);
          this.state = {
              isLoaded: false,
              category: {
                  'published_projects':[{'images':[]}],
                   'unpublished_projects':[{'images':[]}],
              }
          };
      }
      
      componentWillMount(){
          fetch(route('api.categories.show', {'slug':this.props.match.params.slug}))
          .then(response => response.json())
          .then(data => {this.setState({category: data, isLoaded: true});});
      }
      

      最后,在render 方法中,只需执行if (this.state.isLoaded) { return(...); } else { return &lt;p&gt;Loading categories...&lt;/p&gt;; }

      【讨论】:

      • 好主意,谢谢。只是一个细节:你不能在 render 方法中做 if 语句,但你可以做一个 ternaire。
      • 不,您可以在render 方法中创建if 语句,但不能在return 标记中执行此操作
      • @Indranil 如果您在 jsx 中使用三元运算符,例如 {isLoaded ? 'loaded' : 'not loaded'},则可以
      【解决方案4】:

      基于 React 生命周期,componentWillMount 函数在渲染之前被调用...但这并不意味着渲染函数将等待 componentWillMount 内部的 async 调用开始渲染,因此您的组件将渲染一次在提取完成之前和提取完成之后一次。

      如果您想确保您的组件仅在数据可用时才呈现,您可以在父组件中获取数据,然后仅在数据可用时呈现此组件,然后将其作为道具从父组件传递给此组件,这将保证单个渲染

      【讨论】:

        猜你喜欢
        • 2017-07-23
        • 2017-11-03
        • 1970-01-01
        • 1970-01-01
        • 2019-05-26
        • 1970-01-01
        • 2021-07-04
        • 2017-12-29
        • 1970-01-01
        相关资源
        最近更新 更多