如何优化React Native表格组件仅渲染变更行?
开发调研APP时,基于React Native框架用react native paper的DataTable组件展示大量单选按钮,功能正常但存在性能问题:点击某一行的单选按钮时,所有行都会重新渲染,10行以上时卡顿明显。曾尝试用useCallback和memo优化但未成功,现在需要实现仅重新渲染被点击的行。
接口定义:
export interface Subcategory { name: string, scores: Score[] } export interface Score { attribute: string, value: string, }
1. 用React.memo包裹子组件
给MultipleChoiceGridRow和MultipleChoiceGridCell加上React.memo,让子组件只有在props变化时才重新渲染,避免父组件重渲染时子组件无条件跟着更新。
修改子组件代码:
// 包裹MultipleChoiceGridCell const MultipleChoiceGridCell: FunctionComponent<MultipleChoiceGridCellProps> = React.memo(({value, actualValue, onPress}) => { return ( <DataTable.Cell> <RadioButton status={actualValue === value ? "checked" : "unchecked"} onPress={() => onPress(value)} value={value}/> </DataTable.Cell> ) }) // 包裹MultipleChoiceGridRow const MultipleChoiceGridRow: FunctionComponent<MultipleChoiceGridRowProps> = React.memo(({score, possibleValues, onRowChange}) => { console.log("-------rendering row: ", score.attribute); const handleCellPress = (value: string) => { onRowChange({...score, value}) } return ( <DataTable.Row> <DataTable.Cell key={score.attribute}><Text variant="bodyMedium">{score.attribute}</Text></DataTable.Cell> {possibleValues.map(val => <MultipleChoiceGridCell key={score.attribute + "_" + val} value={val} actualValue={score.value} onPress={handleCellPress} /> )} </DataTable.Row> ) })
2. 稳定回调函数并优化状态更新
原来的handleScoreUpdate依赖整个subcategory状态,每次状态变化都会生成新函数,触发所有子组件重渲染。改用函数式更新获取最新状态,同时用useCallback稳定函数引用。另外,用map替代filter+push更新状态,既高效又能保持数组原有顺序。
修改演示屏幕组件的handleScoreUpdate:
const MultipleChoiceGridDemo: FunctionComponent<MultipleChoiceGridDemo> = ({navigation}) => { const [subcategory, setSubcategory] = useState<Subcategory>(INITIAL_STATE) // 用useCallback包裹,依赖项为空,因为用了函数式更新 const handleScoreUpdate = useCallback((newScore: Score) => { setSubcategory(prev => ({ ...prev, scores: prev.scores.map(score => score.attribute === newScore.attribute ? newScore : score ) })) }, []) return ( <View> <MultipleChoiceGrid rows={subcategory} possibleValues={["very good", "good", "poor"]} onTableChange={handleScoreUpdate}/> </View> ); };
3. 避免在render中执行排序
原来的rows.scores.sort写在MultipleChoiceGrid的render方法里,每次渲染都会重复执行排序,浪费性能还可能触发不必要的重渲染。把排序移到状态初始化阶段:
修改演示屏幕组件的初始状态:
const INITIAL_STATE = { name: "Demo", scores: [ {attribute: "row1", value: ""}, {attribute: "row2", value: ""}, {attribute: "row3", value: ""}, {attribute: "row4", value: ""}, {attribute: "row5", value: ""}, ].sort((a, b) => a.attribute.localeCompare(b.attribute)) // 初始化时完成排序 }
然后修改MultipleChoiceGrid组件,去掉render中的排序逻辑:
const MultipleChoiceGrid: FunctionComponent<MultipleChoiceGridProps> = ({rows,possibleValues, onTableChange}) => { console.log("+++++++++ rendering table +++++++++++") return ( <DataTable> <DataTable.Header> <DataTable.Title key="header">{}</DataTable.Title> {possibleValues.map(header => <DataTable.Title key={header}><Text variant="bodyMedium">{header}</Text></DataTable.Title>)} </DataTable.Header> {/* 直接映射已排序的scores数组 */} {rows.scores.map(score => <MultipleChoiceGridRow key={score.attribute} score={score} possibleValues={possibleValues} onRowChange={(newScore) => onTableChange(newScore)}/>)} </DataTable> ); };
4. 优化行内回调函数
handleCellPress在MultipleChoiceGridRow的每次渲染中都会重新创建,给它加上useCallback进一步稳定props,避免触发MultipleChoiceGridCell的不必要重渲染:
修改MultipleChoiceGridRow:
const MultipleChoiceGridRow: FunctionComponent<MultipleChoiceGridRowProps> = React.memo(({score, possibleValues, onRowChange}) => { console.log("-------rendering row: ", score.attribute); // 用useCallback包裹handleCellPress,依赖score和onRowChange const handleCellPress = useCallback((value: string) => { onRowChange({...score, value}) }, [score, onRowChange]) return ( <DataTable.Row> <DataTable.Cell key={score.attribute}><Text variant="bodyMedium">{score.attribute}</Text></DataTable.Cell> {possibleValues.map(val => <MultipleChoiceGridCell key={score.attribute + "_" + val} value={val} actualValue={score.value} onPress={handleCellPress} /> )} </DataTable.Row> ) })
内容的提问来源于stack exchange,提问作者mpopp

