首页 文章

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

提问于
浏览
0

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

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

    问题是组件呈现两次

    你有一个丑陋的,不推荐的选项,它是第一次从 render 1139989(当没有数据被提取时),并且当你有数据时正常返回,防止元素被渲染 .

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

  • 3

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

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

  • 0

    这里的问题可能是 fetch 需要一些时间来从服务器加载数据,因此,DOM会在该时间内加载,并在收回 fetch ed数据后进行更新 .

    要确保组件只加载一次,请添加一个新的状态变量 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>; }

  • 1

    在数据恢复之前,没有办法 delay render method .

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

    • 组件首次安装时的一个

    • 秒异步操作完成后,执行带有setState的回调

    但是,在第一次渲染调用期间,您可以通过返回 null 来执行 prevent the component from being added to the DOM .

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

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

相关问题