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
相关产品推荐
相关产品推荐

