React Native中如何避免setState引发多次重渲染?相关疑问
问题描述
我了解React Native会在调用setState的组件中触发重渲染。经测试,useEffect代码中的7个setState会导致组件重渲染7次,示例代码如下:
const pullData = async (numToPull, filterPull, sorterPull) => { setToSpin(true); const response = await AxiosPost( sc.Slink.getMailInbox, { page: 1, pageSize: numToPull, filterOption: [ ...filterPull, { key: "isUnread", value: true, }, ], sortOption: sorterPull, }, { Authorization: ctxt.authInfo.tokenSF }, { studio: ctxt.roomInfo.currentLoginRoom } ); setToSpin(false); if (response.data.suc) { setShowList(response.data.data.items); setNumOfDataPulled(response.data.data.items.length); setSearchKey(""); setCurrentFilter(filterPull); setCurrentSorter(sorterPull); } else setSmsg(response.data.msg); }; useEffect(() => { pullData(sc.Snumber.numToPullMail, [], []); }, []);
上述示例是项目中的简单场景,我还有一些复杂组件使用了20多个setState,每个组件包含约10个函数。若setState导致20+次重渲染加上函数重建,资源浪费可能相当可观。
我不想使用Redux和useCallback,想了解两个问题:
- 组件重渲染20次在React Native中属于正常情况吗?
- 是否有更巧妙的方法避免虚拟DOM对比的资源浪费?比如上述示例中连续的5个
setState,没有必要触发5次重渲染。
解答
1. 组件重渲染20次属于正常情况吗?
这绝对不是正常情况,属于明显的性能隐患。React的设计逻辑中,虽然每次调用setState都会标记组件需要重渲染,但连续的同步setState调用在多数场景下会被自动合并。但在async/await这类异步代码块中,后续的setState调用会脱离React的批量更新机制,导致多次独立的重渲染。20次重渲染会重复执行虚拟DOM比对和UI更新流程,在复杂组件中会显著拖慢性能,必须优化。
2. 减少重渲染的实用方法
方法一:合并分散的useState为单一状态对象
把关联度高的多个独立useState合并成一个状态对象,通过一次setState更新所有相关值,从而只触发一次重渲染。
修改示例代码如下:
// 初始化合并后的邮件相关状态 const [mailState, setMailState] = useState({ showList: [], numOfDataPulled: 0, currentFilter: [], currentSorter: [], searchKey: "" }); // 保留独立的loading和提示状态(如果和其他状态无强关联) const [toSpin, setToSpin] = useState(false); const [smsg, setSmsg] = useState(""); const pullData = async (numToPull, filterPull, sorterPull) => { setToSpin(true); const response = await AxiosPost( sc.Slink.getMailInbox, { page: 1, pageSize: numToPull, filterOption: [ ...filterPull, { key: "isUnread", value: true, }, ], sortOption: sorterPull, }, { Authorization: ctxt.authInfo.tokenSF }, { studio: ctxt.roomInfo.currentLoginRoom } ); setToSpin(false); if (response.data.suc) { // 一次更新所有邮件相关状态,仅触发一次重渲染 setMailState({ showList: response.data.data.items, numOfDataPulled: response.data.data.items.length, searchKey: "", currentFilter: filterPull, currentSorter: sorterPull }); } else { setSmsg(response.data.msg); } };
方法二:用useReducer管理复杂状态
如果状态逻辑复杂、多个状态间有依赖关系,useReducer比合并useState更易维护。通过reducer函数统一处理所有状态更新,无论需要修改多少个状态属性,都只需要调用一次dispatch,触发一次重渲染。
示例实现:
// 定义初始状态 const initialState = { toSpin: false, showList: [], numOfDataPulled: 0, searchKey: "", currentFilter: [], currentSorter: [], smsg: "" }; // 定义reducer函数,处理所有状态更新逻辑 function mailReducer(state, action) { switch (action.type) { case 'START_LOADING': return { ...state, toSpin: true }; case 'STOP_LOADING': return { ...state, toSpin: false }; case 'UPDATE_MAIL_DATA': return { ...state, showList: action.payload.items, numOfDataPulled: action.payload.items.length, searchKey: "", currentFilter: action.payload.filter, currentSorter: action.payload.sorter }; case 'SET_ERROR_MESSAGE': return { ...state, smsg: action.payload }; default: throw new Error(`Unknown action type: ${action.type}`); } } // 在组件中使用useReducer const [state, dispatch] = useReducer(mailReducer, initialState); const pullData = async (numToPull, filterPull, sorterPull) => { dispatch({ type: 'START_LOADING' }); const response = await AxiosPost( // 原有请求代码不变 sc.Slink.getMailInbox, { page: 1, pageSize: numToPull, filterOption: [ ...filterPull, { key: "isUnread", value: true }, ], sortOption: sorterPull, }, { Authorization: ctxt.authInfo.tokenSF }, { studio: ctxt.roomInfo.currentLoginRoom } ); dispatch({ type: 'STOP_LOADING' }); if (response.data.suc) { dispatch({ type: 'UPDATE_MAIL_DATA', payload: { items: response.data.data.items, filter: filterPull, sorter: sorterPull } }); } else { dispatch({ type: 'SET_ERROR_MESSAGE', payload: response.data.msg }); } };
方法三:手动批量更新状态(React 18+)
在React 18+中,大多数场景下setState会自动批量更新,但在非React管理的异步操作(比如原生Promise回调、setTimeout)中,可能需要手动触发批量更新。可以使用React Native提供的unstable_batchedUpdatesAPI:
import { unstable_batchedUpdates } from 'react-native'; // 在pullData的成功回调中使用 if (response.data.suc) { unstable_batchedUpdates(() => { setShowList(response.data.data.items); setNumOfDataPulled(response.data.data.items.length); setSearchKey(""); setCurrentFilter(filterPull); setCurrentSorter(sorterPull); }); }
虽然API名称带有"unstable",但它在实际项目中已经被广泛使用,后续很大概率会转为稳定API。
内容的提问来源于stack exchange,提问作者user20499181

