父组件在父状态更改时不必要地重新渲染子对象

时间:2019-08-03 17:44:17

标签: javascript reactjs react-hooks

我正在创建一个简单的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并消除了所选卡的本地状态,因此我无法在列表中突出显示活动卡。根据我对react的理解,我不明白为什么ScrollView重新呈现,因为它不依赖于displayCard的状态。我不确定采取什么方法来解决它。当我单击列表中的卡时,我希望它突出显示红色。

3 个答案:

答案 0 :(得分:0)

默认情况下(无状态)组件会在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);

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

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

答案 1 :(得分:0)

子组件的render方法一旦被调用,它的父组件的render方法将被调用。如果道具或状态发生变化,也是如此。

由于您正在使用功能组件,因此可以使用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生命周期方法。

答案 2 :(得分:-2)

因此,您的App组件应该保留用户单击的卡的状态?现在,您的App组件是无状态的。这是一个功能组件。尝试将其转换为具有初始状态和维护状态的类组件。

setDisplayCard()的逻辑是什么?

我在React 16中听说过吗?诸如“ useState()”和“挂钩”之类的东西,但我对此并不熟悉。

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

React functional component using state