简体   繁体   English

尽管使用了 React.memo(),但 React 功能组件仍会重新渲染

[英]React functional component rerenders despite use of React.memo()

I'm fairly new to React and I'm having some trouble understanding exactly why an unchanging component is getting rerendered, even though I'm using the React.memo higher-order component.我对 React 还很陌生,即使我使用的是React.memo高阶组件,我也无法准确理解为什么会重新渲染不变的组件。

I have a sidebar which contains a number of row elements.我有一个包含许多行元素的侧边栏。 Rows contain data that's used in other components;行包含在其他组件中使用的数据; all components share the 'selection' status of the rows.所有组件共享行的“选择”状态。 In the sidebar, I change the styling to show the selection state of every element.在侧边栏中,我更改样式以显示每个元素的选择 state。

Everything behaves as expected, but performance scales poorly as the list gets longer.一切都按预期运行,但随着列表变长,性能扩展很差。 I think part of this is due to React re-rendering every row element in the sidebar list, including ones whose selection state has not changed.我认为部分原因是 React 重新渲染了侧边栏列表中的每个行元素,包括选择 state 的元素没有改变。 I thought I could prevent this re-rendering by using React.memo , but it doesn't seem to make a difference.我想我可以通过使用React.memo来防止这种重新渲染,但这似乎没有什么不同。

Here is the code for each list entry:以下是每个列表条目的代码:

import React from 'react';

// The only props that might change value are the labels string and
// the styles rowStyle and labelStyle, which caller populates
// with 'selected' or 'unselected' styles based on row state
const Row = React.memo(({
    rowId, labels = "", rowStyle = {}, labelStyle = {},
    onClicked // callback from grandparent, which updates selections (w/ modifier keys)
}) => {
    console.log(`Rendering row ${rowId}`) // to report when rows rerender
    return (
        <div
            key={rowId}
            style={rowStyle}
            onClick={(event) => onClicked(rowId, event)}
        >
            <span>{rowId}</span>
            <span style={labelStyle}>{ labels }</span>
        </div>
    );
})

export default Row;

This component is called from a parent which represents the entire sidebar list.该组件是从代表整个侧边栏列表的父级调用的。 In order to minimize the amount of needless function calls (and make very clear that there's nothing with any side effects happening within the individual rows), I build a list of tuples for each row that has its id, style, labels, and label-style.为了尽量减少不必要的 function 调用的数量(并且非常清楚在各个行中没有发生任何副作用),我为每一行构建了一个元组列表,其中包含其 id、样式、标签和标签-风格。

The contents of the list are passed to the Row component, and most of the time should be identical between calls (thus triggering memoization and avoiding the rerender), but don't seem to be.列表的内容被传递给Row组件,并且大多数时候调用之间应该是相同的(从而触发记忆并避免重新渲染),但似乎不是。

import React from 'react';
import Row from '../pluginComponents/Row';
import Styles from './common/Styles'; // to ensure the references aren't changing

// onClicked is passed in from the parent component and handles changing the selections
const ListDiv = React.memo(({ rowIds, onClicked, rowLabels, styling, selections }) => {
    const tuples = rowIds.reduce((priors, rowId) => {
        return {
            ...priors,
            [rowId]: {
                'style': Styles.unselectedStyle,
                'labelStyle': Styles.unselectedLabelStyle,
                'labels': ((rowLabels[rowId] || {}).labels || []).join(", ")
            }
        }
    }, {});
    Object.keys(selections).forEach((rowId) => {
        if (!tuples[rowId]) return;
        tuples[rowId]['style'] = Styles.selectedStyle;
        tuples[rowId]['labelStyle'] = Styles.selectedLabelStyle;
    });
    return (
        <div style={styling}>
            {rowIds.map((rowId) => (
                <Row
                    key={rowId}
                    rowId={rowId}
                    labels={tuples[rowId]['labels']}
                    rowStyle={tuples[rowId]['style']}
                    labelStyle={tuples[rowId]['labelStyle']}
                    onClicked={onClicked}
                />
            ))}
        </div>
    )
})

const RowList = ({ list, selections = {}, onClicked, labels={}, styling }) => {
    if (!list) return (<div>Empty list</div>);
        
    return (
        <div>
            <ListDiv
                rowIds={list}
                onClicked={onClicked}
                rowLabels={labels}
                styling={styling}
                selections={selections}
            />
        </div>
    );
}

export default RowList;

which is itself called from a grandparent class that manages all the state:它本身是从管理所有 state 的祖父母 class 调用的:

const Grandparent = (props) => {
  ...
  return (
    ...
    <div>
      {
        (status !== 'complete') ? (
          <div><CircularProgress /></div>
        ) : (
          <RowList list={data.list}
            selections={selections} // tracked with useState
            onClicked={handleClicked} // calls some functions defined in this component
            labels={data.labels || {}}
            styling={foo}
          />
        )
      }
    ...
  );
...

Why are my ought-to-be-memoized entries of the Row component getting rerendered, and what can I do to fix it?为什么我应该记住的Row组件条目被重新渲染,我能做些什么来修复它?

The onClicked function in the Grandparent could be getting recreated on each render, so making your row component re-render as well.祖父母中的 onClicked function 可能会在每次渲染时重新创建,因此您的行组件也会重新渲染。

The solution is to use React.useCallback in the Grandparent.解决方案是在祖父母中使用 React.useCallback。

const handleClicked = React.useCallback(() => {
    ...
}, [a, b])

Where a and b are dependencies that if change will require a re-render.其中 a 和 b 是依赖项,如果更改将需要重新渲染。

React useCallback docs反应 useCallback文档

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM