在componentWillMount中,只有一个setState,react组件呈现两次

时间:2017-10-23 16:13:23

标签: javascript reactjs

我有这个组件,其角色是从服务器获取带有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>
    );
}

}

4 个答案:

答案 0 :(得分:3)

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

// 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(当没有数据被提取时),并且当你有数据时正常返回,防止元素被呈现。

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

答案 1 :(得分:1)

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

如果你想确保你的组件只有在数据可用的情况下才会呈现,你可以在父组件中获取数据,然后只有在数据可用时才渲染这个组件,然后将它作为道具传递给父组件,这将保证单个渲染

答案 2 :(得分:1)

这里的问题可能是fetch花费一些时间从服务器加载数据,因此,DOM在该时间内加载,并在fetch数据为带回来了。

要确保组件仅加载一次,请添加新的状态变量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 <p>Loading categories...</p>; }

答案 3 :(得分:0)

延迟 调用 呈现 方法,直到数据恢复为止。

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

  • 组件首次安装时的一个
  • 当您的异步操作完成并且使用setState执行回调

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

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

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