You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

阻止列表选中元素时整体重渲染的React性能优化问题

React列表性能优化方案

问题

选中列表项时,仅需新选中项和前选中项重渲染,但当前数百条规模的列表会整体重渲染,导致页面卡顿。已尝试React.memo但未达到预期优化效果。

原因分析

  1. 使用index作为列表项key:index作为key无法让React精准识别列表项的唯一身份,且在列表结构变化时会引发不必要的组件卸载/重建;
  2. 动态生成style对象传递给子组件:在TermineList的map循环中,每次渲染都会创建新的style对象,React.memo的浅比较会判定props发生变化,导致所有子组件强制重渲染;
  3. 样式逻辑集中在父组件:父组件控制所有子组件的样式,导致每个子组件都依赖全局的currentlySelected状态,只要该状态变化,所有子组件的props都会被重新计算。

优化方案

  1. 使用数据项唯一ID作为key:用termin.id替代index,让React能精准跟踪列表项的身份变化;
  2. 将样式逻辑移至子组件内部:子组件根据自身数据ID和全局选中状态自行计算样式,避免父组件传递动态生成的style对象;
  3. 确保props稳定性:利用useState返回的set函数的稳定性,以及不可变的数据引用,让React.memo的浅比较逻辑正确生效。

修改后的代码示例

父组件(SubSiteDokumentation)

原有逻辑无需修改:

export default function SubSiteDokumentation(props: any) {
    const StammdatenContext = React.useContext(ProbandContext);
    const [selectedId, setSelectedId] = React.useState("");
    const { formData, handleGetDetail, isLoading, isError } = useAPIEndpoint({
        endpoint: "KontaktetermineList",
        formName: "Termine",
        pk: StammdatenContext.currentPnr,
        enableSnackbar: true,
        initialLoading: true,
    });
    React.useEffect(() => {
        handleGetDetail();
    }, []);
    if (isError) {
        return (
            <Typography variant="h4" align="center" color="red">
                Termine konnten nicht geladen werden! Versuchen sie es später erneut
                oder wenden sie sich an den Administrator
            </Typography>
        );
    }
    if (isLoading) {
        return <Spinner animation="border"></Spinner>;
    }
    return (
        <Grid container spacing={2}>
            <Grid item xs={7}>
                <TermineList
                    data={formData}
                    changeSelected={setSelectedId}
                    currentlySelected={selectedId}
                ></TermineList>
            </Grid>
            {/* <Grid item xs={5}>
                <TerminDetail currentlySelected={selectedId}></TerminDetail>
            </Grid> */}
        </Grid>
    );
}

列表组件(TermineList)

替换key为termin.id,移除style传递,改为传递全局选中状态和索引:

function TermineList(props: any) {
    return (
        <Box
            pl={"10px"}
            sx={{
                background: "#b4f5bf",
            }}
            className="my-sidebarAuftraege my-nice-scrollbarBox"
        >
            <ListSubheader sx={{ background: "#b4f5bf", zIndex: "1000" }}>
                <Typography align="center" variant="h5" color="black">
                    Dokumentation
                </Typography>
            </ListSubheader>
            <List
                component="div"
                disablePadding
                sx={{ background: "#e3ffe9", borderRadius: "10px" }}
            >
                {props.data.map((termin: any, index: number) => {
                    return (
                        <React.Fragment key={termin.id}>
                            <TermineListEntry
                                termin={termin}
                                changeSelected={props.changeSelected}
                                currentlySelected={props.currentlySelected}
                                index={index}
                            />
                            <Divider
                                sx={{ borderBottomWidth: 2, backgroundColor: "black" }}
                            ></Divider>
                        </React.Fragment>
                    );
                })}
                <ListItem
                    sx={{
                        display: "flex",
                        justifyContent: "center",
                        backgroundColor:
                            (props.data.length % 2 === 1 && "#00000011") || "#00000000",
                    }}
                >
                    {/* <Box sx={{ display: "flex", justifyContent: "center" }}>
                        <div>test</div>
                    </Box> */}
                </ListItem>
            </List>
        </Box>
    );
}

列表项组件(TermineListEntry)

内部计算样式,保留React.memo:

const TermineListEntry = React.memo((props: any) => {
    // 子组件内部计算样式,避免父组件传递新对象
    const style = {
        backgroundColor: props.index % 2 === 1 ? "#00000011" : "",
        borderColor: "#89ca07",
        borderBottom: props.currentlySelected === props.termin.id ? "dashed" : "",
    };

    return (
        <ListItemButton
            style={style}
            onClick={() => {
                props.changeSelected(props.termin.id);
            }}
        >
            <ListItemText>
                <Grid container>
                    <Grid item xs={2}>
                        {Moment(props.termin.datum).format("DD.MM.YYYY")}
                    </Grid>
                    <Grid item xs={2}>
                        {props.termin.uhrzeit}
                    </Grid>
                    <Grid item xs={6}>
                        {props.termin.inhalt}
                    </Grid>
                    <Grid item xs={1}>
                        {props.termin.art}
                    </Grid>
                    <Grid item xs={1}>
                        {props.termin.kv}
                    </Grid>
                </Grid>
            </ListItemText>
        </ListItemButton>
    );
});

额外优化建议

  • 若列表数据量超过千条,可引入虚拟滚动(如react-window或Material-UI的VirtualizedList),仅渲染可视区域内的列表项,大幅降低渲染压力;
  • 确保formData为不可变数据,避免数据引用变化触发父组件不必要的重渲染。

内容的提问来源于stack exchange,提问作者Zergoholic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 19:20:49