【问题标题】:Why is my React component rendering twice?为什么我的 React 组件渲染两次?
【发布时间】:2020-09-07 20:20:19
【问题描述】:

我使用React-hooksContext API 进行状态管理。当consumer 组件呈现时,我已经控制台注销了context 状态。在console 中,我注意到context 的状态被注销了两次。这是为什么?

我正在使用fetch API 从我的本地node 服务器中提取数据。所以,在console 的前几行中,我得到了context 的初始状态,例如undefinednull——然后就在它下面,我得到了从服务器拉入数据的更新状态。

我创建了一个“大师”context 来分享functions,我将在整个应用程序中使用它。 fetch 函数使用的是async/await


import React, { Component, createContext } from 'react';

export const MasterContext = createContext();

export class MasterProvider extends Component {
    state = {
        fetchData: async (url) => {
            let res = await fetch(url);
            let data = await res.json();
            return data;
        }
    }

    render() { 

        return ( 
            <MasterContext.Provider value={{...this.state}}>
                {this.props.children}
            </MasterContext.Provider>
         );
    }
}



我有两个组件,使用两个不同的 contexts - 一个 context 非常简单


state = {
        title: '',
        body: '',
        cta: {
            text: ''
        },
        img: '',
        setHeaderPromo: (res) => {
            this.setState({
                title: res[0].title,
                body: res[0].body,
                cta: {...this.state.cta, ...res[0].cta},
                img: res[0].img
            });
        }
    }

为此组件提取的数据只是一个简单的array 和一个object

这是consumer 组件:


const HeaderPromo = () => {
    const {title, body, cta, img, setHeaderPromo} = useContext(HeaderContext);
    const {fetchData} = useContext(MasterContext);

    useEffect(() => {

        fetchData(`http://localhost:5000/api/header`)
            .then((res) => {
                setHeaderPromo(res);
        });
    }, [fetchData, setHeaderPromo]);

    console.log(title);

    return ( 
        <article className="cp-header__promo">
            <div className="cp-header__promo__image">
                <img src={img} alt="promo"/>
            </div>
            <div className="cp-header__promo__copy">
                <h3>{title}</h3>
                <p>{body}</p>
                <button>{cta.text}</button>
            </div>
        </article>
     );
}

所以,这在技术上是可行的。但是,我注意到当我注销 title 变量时,它会输出两次。第一次是context的初始状态,第二次输出数据的内容。为什么要这样做?

我的问题在于我的第二个组件 - context 状态只是一个空的 array,在 fetch 请求之后被填充。

第二个context

 state = {
        albums: null,
        setNewReleases: (res) => {
            this.setState({
                albums: res
            });

        }
    }

这是两个consumer 组件:

const NewReleases = () => {

    const {albums, setNewReleases} = useContext(NewReleaseContext);
    const {fetchData} = useContext(MasterContext);

    useEffect(() => {

        fetchData(`http://localhost:5000/api/new-releases`)
            .then((res) => {
                setNewReleases(res);
            });

    }, [fetchData, setNewReleases]);

  console.log('from newRelease component', albums);

    return ( 
        <section className="cp-new-releases">
            <Row sectionHeading={`New Releases`} albums={albums}/>
        </section>
     );
}

export default NewReleases;

因此,albums 变量再次被注销两次。首先,初始状态,然后数据被拉入,并再次记录。

现在,我将这个albums 变量作为prop 传递给&lt;Row/&gt; 组件。

行组件:

const Row = ({sectionHeading, albums}) => {

    console.log('from row component', albums);

    return ( 
        <Fragment>
            <div className="cp-new-releases__row">
            {(sectionHeading) && (<h3 className="row-title">{sectionHeading}</h3>)}
                <div className="cp-new-releases__row__album-container">

                {albums.map((item, index) => (
                        <Album img={item.img} title={item.title} key={index}/>
                  ))}

                </div> 
            </div>
        </Fragment>
     );
}

export default Row;

现在albums 变量是一个包含两个objectsarray。如果我尝试遍历array,它会抛出错误Uncaught TypeError: Cannot read property 'map' of null

我可以解决这个问题的唯一方法是通过if 检查albums 是否有一个值。


const Row = ({sectionHeading, albums}) => {

    console.log('from row component', albums);

    return ( 
        <Fragment>
            <div className="cp-new-releases__row">
            {(sectionHeading) && (<h3 className="row-title">{sectionHeading}</h3>)}
                <div className="cp-new-releases__row__album-container">
                  {(albums) ? (
                    albums.map((item, index) => (
                        <Album img={item.img} title={item.title} key={index}/>
                  ))
                  ) : (<p style={{color: 'red'}}>no album</p>)}
                </div> 
            </div>
        </Fragment>
     );
}

export default Row;

它试图循环遍历context 的初始状态。有人可以解释发生了什么,以及如何改进我的代码吗?

我知道这是啰嗦。所以,如果你坚持到了最后,你就是冠军。

谢谢

【问题讨论】:

  • fetchData 是异步的,所以你有你的初始渲染,然后当异步完成更新状态时另一个。您可以检查相册是否为空,并且不渲染任何子组件,然后当您的 fetch 返回并设置相册时,渲染器可以添加您的组件

标签: javascript reactjs react-hooks react-context


【解决方案1】:

FetchData 是一个异步操作 - 所以您的问题是您在从服务器获取数据之前添加了 Row 组件。在您收集相册之前,请暂停渲染&lt;Row&gt;

return ( 
        <section className="cp-new-releases">
          { albums && // you might also want to test length here 
                      // - unless you want to show a message in your 
                      // Row component, indicating that non were found.
            <Row sectionHeading={`New Releases`} albums={albums}/>
          }
        </section>
     );

现在您的 Row 元素不是在初始加载时创建的,而是等到您获得数据后再添加。想象一下,调用一个函数,你不会不必要地调用它,并且只使用有效数据。最终,这就是您将组件添加到渲染函数时所做的事情。所以你必须添加条件逻辑,因为组件是有效的函数,应该根据你的特定用例的逻辑来调用。

【讨论】:

  • 非常感谢!现在说得通了
猜你喜欢
  • 2022-10-02
  • 2017-11-22
  • 1970-01-01
  • 2020-03-17
  • 2019-05-26
  • 1970-01-01
  • 2020-11-22
  • 2020-01-03
相关资源
最近更新 更多