簡體   English   中英

react組件在componentWillMount中僅使用一個setState渲染兩次

[英]react component renders twice with only one setState in componentWillMount

我有這個組件,該角色的作用是從服務器獲取帶有fetch的類別對象,並將其放入componentWillMount中。 只有一個setState:當數據從服務器到達時,它位於componentWillMount承諾回調中。

問題在於,該組件第一次呈現兩次,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>
    );
}

}

您的代碼可能要求兩次間接渲染:

// 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. 

問題是該組件渲染兩次

您有一個丑陋的,不建議使用的選項,該選項在首次render (沒有獲取數據時)從render return null ,並在有數據時正常返回,從而阻止了渲染該元素。

您還有另一個選擇是捕獲是否在其父級中獲取數據,然后將元素隱藏在那里。

基於React生命周期, componentWillMount函數在渲染之前被調用...但這並不意味着渲染函數將等待componentWillMount內部的async調用開始渲染,因此您的組件將在獲取完成之前渲染一次,在獲取之后渲染一次已經完成了。

如果要確保僅在數據可用時才可以渲染組件,則可以在父組件中獲取數據,然后僅在數據可用時渲染該組件,然后將其作為道具從父級傳遞到該組件,這將確保單個渲染

這里的問題可能是, fetch需要花費一些時間來從服務器加載數據,因此DOM在該時間內加載,並且一旦將fetch數據重新帶回就進行了更新。

要確保組件僅加載一次,請添加一個新的狀態變量isLoaded或其他內容,並將其設置為false。 然后, fetch完成后,將其設置為true ,並且僅在isLoaded為true時才呈現組件。

這是一個代碼示例:

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>; } if (this.state.isLoaded) { return(...); } else { return <p>Loading categories...</p>; }

在數據返回之前,沒有辦法延遲render 方法調用

在執行渲染之前,組件不會等待異步操作完成。 因此,您至少要進行2次渲染調用:

  • 第一次安裝組件時一個
  • 其次,當您的異步操作完成並且執行帶有setState的回調時

但是,您可以做的是通過返回null 防止在第一次渲染調用期間將組件添加到DOM中。

您可以通過在狀態中添加一個加載標志,然后在數據返回時翻轉它來實現此目的……然后將render方法修改為如下所示:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM