【问题标题】:Parent component unnecessarily re-rendering child on parent state change父组件在父状态更改时不必要地重新渲染子组件
【发布时间】:2019-08-03 17:44:17
【问题描述】:

我正在创建一个简单的 Magic The Gathering 搜索引擎。愿景是拥有一个搜索结果列表,当点击搜索结果时,主显示屏会呈现有关所选卡片的扩展信息。

You can see it here

顶级App 组件包含要显示的卡片的状态,ScrollView 组件维护所选卡片的状态,仅在列表中突出显示所选卡片。我向下传播setDisplayCard 处理程序,这样当在列表中单击卡片时,我可以将显示卡片设置为回调。

function App(props) {

  const [displayCard, setDisplayCard] = useState(null)

  return (
    <div className="App">
      <SearchDisplay handleCardSelect={setDisplayCard}/>
      <CardDisplay card={displayCard} />
    </div>
  );
}
function SearchDisplay({handleCardSelect}) {

    const [cards, setCards] = useState([]);

    useEffect(() => {
        (async () => {
            const cards = await testCardSearch();
            setCards(cards);
        })();
    }, []);

    async function handleSearch(searchTerm) {
        const searchCards = await cardSearch({name: searchTerm});
        setCards(searchCards)
    };

    return (
        <StyledDiv>
            <SearchBar 
                handleSubmit={handleSearch}
            />
            <ScrollView 
                handleCardSelect={handleCardSelect} 
                cards={cards}
            />
        </StyledDiv>
    );
}
function ScrollView({cards, handleCardSelect}) {

    const [selected, setSelected] = useState(null);

    return (
        <ViewContainer>
            {cards.map((card, idx) =>
                <li 
                    key={idx} 
                    style={selected === idx ? {backgroundColor: "red"} : {backgroundColor: "blue"}}
                    onClick={() => {
                        setSelected(idx);
                        handleCardSelect(card);
                    }}
                >
                    <Card card={card} />
                </li>
            )}
        </ViewContainer>
    );
}

我遇到的问题是调用setDisplayCard 会重新呈现我的ScrollView 并消除其所选卡的本地状态,因此我无法突出显示列表中的活动卡。根据我对反应的理解,我不明白为什么ScrollView 会重新渲染,因为它不依赖于displayCard 的状态。而且我不确定采取什么方法来解决它。当我点击列表中的一张卡片时,我希望它会突出显示为红色。

【问题讨论】:

    标签: javascript reactjs react-hooks


    【解决方案1】:

    子组件的render 方法总是会被调用,只要它的父组件的render 方法被调用。如果它的 props 或 state 发生变化也是如此。

    由于您使用的是函数式组件,您可以使用React.memo HOC 来防止不必要的组件重新渲染。

    React.memo 的行为类似于 PureComponent,并且会将 ScrollView 的旧 props 与新的 props 进行浅显比较,并且只有在它们不相等时才会触发重新渲染:

    export default React.memo(ScrollView);
    

    React.memo 还有第二个参数,让您可以控制比较:

    function areEqual(prevProps, nextProps) {
      // only update if a card was added or removed
      return prevProps.cards.length === nextProps.cards.length;
    }
    
    export default React.memo(ScrollView, areEqual);
    

    如果您要使用基于类的组件,您也可以使用shouldComponentUpdate 生命周期方法。

    【讨论】:

    • 谢谢。我认为所有组件都像 PureComponents 一样运行。我不认为父渲染总是需要子渲染。
    【解决方案2】:

    默认情况下(无状态)组件在 3 个条件下重新渲染

    1. 它的道具变了
    2. 它的状态已经改变
    3. 父级重新渲染

    可以使用shouldComponentUpdate 更改此行为,或者使用memo 更改无状态组件。

    // If this function returns true, the component won't rerender
    areEqual((prevProps, nextProps) => prevProps.cards === nextProps.card)
    
    export default React.memo(ScrollView, areEqual);
    

    但是,我认为这不是您的问题。您使用数组 Index idx 作为元素键,这通常会导致意外行为。

    尝试删除 key={idx} 并检查是否可以解决您的问题。

    【讨论】:

      【解决方案3】:

      所以你的 App 组件应该保存用户点击的卡片的状态?现在你的 App 组件是无状态的。它是一个功能组件。尝试将其转换为具有初始状态和维护状态的类组件。

      你的 setDisplayCard() 的逻辑是什么?

      我在 React 16 中听说过?有'useState()'和'hooks'之类的东西,但我不熟悉。

      这个人似乎也有类似的问题,

      React functional component using state

      【讨论】:

      • useState()App() 中。它将功能组件转变为静态组件。
      • 好的,CardDisplay 组件是否应该专门用于在侧面显示“更多详细信息”卡片?此外,当您单击卡片并刷新所有内容时,您是否看到“CardDisplay”组件呈现?根据反应的工作方式,当状态改变时,函数或类会重新渲染。因此,因为您更改了“App”的状态,它会重新渲染“SearchDisplay”,从而重新渲染“ScrollView”。如果您想在滚动中突出显示该卡片,同时在右侧显示其“更多详细信息”,“ScrollView”还需要知道选择了哪张卡片。
      猜你喜欢
      • 1970-01-01
      • 2022-06-29
      • 2021-04-06
      • 2020-12-30
      • 1970-01-01
      • 1970-01-01
      • 2016-02-23
      • 2019-09-21
      • 2018-08-14
      相关资源
      最近更新 更多