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

React Native组件新增Redux记录时重复渲染的性能优化问题

解决Redux useSelector导致React Native组件不必要重渲染的问题

问题描述

使用Redux管理状态的React Native组件中,每次向store添加新记录时,FlashList内的所有RecordCard组件都会重复渲染,即便这些已有记录的内容没有变化。已尝试shallowEqual、React.memo、useCallback等优化手段,但问题依然存在。

代码示例

组件代码

const RecordsScreen = () => {
    const records: { [key: string]: SingleRecord[] } = useSelector(selectFilteredRecords, shallowEqual);

    const keys = Object.keys(records);
    const keyExtractor = useCallback((item: any, index: number) => index, []);

    class RenderItem extends React.PureComponent {
        render() {
            const { dateString } = this.props;
            const dateRecords = records[dateString];
            return (
                <Collapsible>
                    <FlashList
                        data={dateRecords}
                        keyExtractor={keyExtractor}
                        renderItem={({ item }) => (<RecordCard record={item} />)}
                        removeClippedSubviews={true}
                        estimatedItemSize={dateRecords.length === 0 ? 1 : dateRecords.length}
                    />
                </Collapsible>
            );
        }
    }

    function areEqual(prevProps: any, nextProps: any) {        
        return JSON.stringify(prevProps) === JSON.stringify(nextProps);
    }
    const Memoized = memo(RenderItem, areEqual);

    return (
        <View>
            <FlashList
                data={keys}
                keyExtractor={keyExtractor}
                renderItem={({ dateString }) => (<Memoized dateString={dateString} />)}
                removeClippedSubviews={true}
                estimatedItemSize={keys.length === 0 ? 1 : keys.length}
            />
        </View>
    );
};

export default RecordsScreen;

Redux代码

interface RecordsState {
    records: { [key: string]: SingleRecord[] }; // key为日期字符串
    recordsFilter: Filter;
}

const initialState: RecordsState = {
    records: {},
    recordsFilter: new Filter()
};

// 使用Reselect创建选择器
export const selectFilteredRecords = createSelector(
    selectRecords,
    selectRecordsFilter,
    (records, filter) => {
        return filterRecords(records, filter);
    }
);

const filterRecords = (records: any, filter: any) => {
    const filteredRecords: { [key: string]: SingleRecord[] } = {};

    if (records !== null && records !== undefined) {
        Object.entries(records).forEach(([dateKey, records]: any) => {
            const filteredRecords = records.filter((record: any) => {
                if (filter.fromDate && record.date! < new Date(filter.fromDate)) {
                    return false;
                }
                if (filter.toDate && record.date! > new Date(filter.toDate)) {
                    return false;
                }
                if (filter.account && record.account !== filter.account) {
                    return false;
                }
                if (filter.category && record.category !== filter.category) {
                    return false;
                }
                if (filter.subCategory && record.subCategory !== filter.subCategory) {
                    return false;
                }
                if (filter.currency && filter.currency !== record.currency) {
                    return false;
                }
                return true;
            });
            if (filteredRecords.length > 0) {
                filteredRecords[dateKey] = filteredRecords;
            }
        });
    }
    return filteredRecords;
}

解决方案

1. 修复Reselect选择器的引用稳定性问题

当前filterRecords每次都会生成全新的对象和数组,即便数据内容未变化,shallowEqual也会判定为新值,触发组件重渲染。需要修改选择器逻辑,尽可能复用原有数据的引用:

const filterRecords = (records: { [key: string]: SingleRecord[] }, filter: Filter) => {
    const filteredRecords: { [key: string]: SingleRecord[] } = {};
    let hasChanges = false;

    if (records) {
        Object.entries(records).forEach(([dateKey, dateRecords]) => {
            // 检查当前日期下的所有记录是否都符合过滤条件,是则直接复用原数组引用
            let allMatch = true;
            for (const record of dateRecords) {
                if (filter.fromDate && record.date! < new Date(filter.fromDate)) { allMatch = false; break; }
                if (filter.toDate && record.date! > new Date(filter.toDate)) { allMatch = false; break; }
                if (filter.account && record.account !== filter.account) { allMatch = false; break; }
                if (filter.category && record.category !== filter.category) { allMatch = false; break; }
                if (filter.subCategory && record.subCategory !== filter.subCategory) { allMatch = false; break; }
                if (filter.currency && filter.currency !== record.currency) { allMatch = false; break; }
            }

            if (allMatch) {
                filteredRecords[dateKey] = dateRecords;
            } else {
                const filtered = dateRecords.filter(record => {
                    if (filter.fromDate && record.date! < new Date(filter.fromDate)) return false;
                    if (filter.toDate && record.date! > new Date(filter.toDate)) return false;
                    if (filter.account && record.account !== filter.account) return false;
                    if (filter.category && record.category !== filter.category) return false;
                    if (filter.subCategory && record.subCategory !== filter.subCategory) return false;
                    if (filter.currency && filter.currency !== record.currency) return false;
                    return true;
                });
                if (filtered.length > 0) {
                    filteredRecords[dateKey] = filtered;
                    hasChanges = true;
                }
            }
        });
    }

    // 若过滤结果与原数据结构完全一致,直接返回原数据引用
    if (!hasChanges && Object.keys(filteredRecords).length === Object.keys(records).length) {
        return records;
    }

    return filteredRecords;
};

2. 解决RenderItem的闭包与组件定义问题

将RenderItem移到组件外部,避免每次父组件重渲染时重新定义组件;同时将dateRecords作为props传入,避免闭包引用外部变量导致的意外更新:

// 移到组件外部,确保引用稳定
const RenderItem = ({ dateRecords }: { dateRecords: SingleRecord[] }) => {
    const keyExtractor = useCallback((item: SingleRecord) => item.id || String(item.date), []);
    return (
        <Collapsible>
            <FlashList
                data={dateRecords}
                keyExtractor={keyExtractor}
                renderItem={({ item }) => <RecordCard record={item} />}
                removeClippedSubviews={true}
                estimatedItemSize={100} // 设置为实际估算的组件高度,而非数组长度
            />
        </Collapsible>
    );
};

// 自定义浅比较逻辑,避免JSON.stringify的性能损耗
const MemoizedRenderItem = memo(RenderItem, (prevProps, nextProps) => {
    return prevProps.dateRecords === nextProps.dateRecords;
});

const RecordsScreen = () => {
    const records = useSelector(selectFilteredRecords);
    const keys = Object.keys(records);
    const keyExtractor = useCallback((dateString: string) => dateString, []);

    return (
        <View>
            <FlashList
                data={keys}
                keyExtractor={keyExtractor}
                renderItem={({ item: dateString }) => (
                    <MemoizedRenderItem dateRecords={records[dateString]} />
                )}
                removeClippedSubviews={true}
                estimatedItemSize={100}
            />
        </View>
    );
};

export default RecordsScreen;

3. 优化RecordCard组件的记忆化

确保RecordCard是纯组件,使用React.memo并自定义精准的比较逻辑:

const RecordCard = memo(({ record }: { record: SingleRecord }) => {
    // 组件内容
}, (prevProps, nextProps) => {
    // 仅比较关键属性,避免不必要的重渲染
    return prevProps.record.id === nextProps.record.id &&
           prevProps.record.amount === nextProps.record.amount &&
           prevProps.record.date === nextProps.record.date;
});

4. 保证Redux状态的Immutable更新

添加新记录时,必须返回全新的对象/数组,避免直接修改原状态:

// 示例reducer逻辑
case ADD_RECORD:
    const newRecord = action.payload;
    const dateKey = formatDate(newRecord.date); // 生成统一格式的日期键
    return {
        ...state,
        records: {
            ...state.records,
            [dateKey]: [...(state.records[dateKey] || []), newRecord]
        }
    };

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 09:27:55