React Native应用性能优化实战技巧
在开发React Native应用时,性能优化是确保用户体验流畅的关键。本文将系统介绍一系列实战技巧,涵盖状态管理、组件渲染、列表处理、缓存策略等方面,帮助开发者避免常见性能陷阱,提升应用响应速度。
1. 全局状态Redux优化
使用Redux Toolkit的createSelector函数可以从Redux状态中选择特定数据,并通过memoization机制避免在相同输入下重复渲染。例如,创建一个认证状态选择器:
const isAuthenticatedSelector = createSelector(
(state) => state.user,
(user) => user.isAuthenticated,
);
const isAuthenticated = useSelector(isAuthenticatedSelector);
2. 合并多个Dispatch与setState
React框架会自动合并连续的setState调用,但对于dispatch操作,合理组织代码也能减少渲染次数。在更新用户信息时,可以合并多个dispatch:
dispatch(userActions.SET_THEME(themeData));
dispatch(userActions.updateOprInfo({ oprInfo }));
if (oprInfo.currency) {
dispatch(i18nActions.updateCurrency({ currency: oprInfo.currency }));
}
if (oprInfo.currencySymbol) {
dispatch(
i18nActions.updateCurrencySymbol({
currencySymbol: oprInfo.currencySymbol,
}),
);
}
3. 使用React.memo减少子组件渲染
React.memo仅检查组件props的变更。当子组件不依赖父组件的参数时,使用memo可以避免不必要的渲染:
import React, { useState, memo } from 'react';
const Child = (props) => {
console.log('子组件渲染');
return <div>子组件</div>;
};
const ChildMemo = memo(Child);
export default () => {
const [count, setCount] = useState(0);
return (
<>
<button onClick={(e) => { setCount(count + 1) }}>+</button>
<p>计数器:{count}</p>
<ChildMemo />
</>
);
};
4. 利用useMemo优化高开销计算
useMemo在依赖项改变时才重新计算值,类似于Vue的computed属性,避免每次渲染都进行重复计算:
import React, { useState, useMemo } from 'react';
const Child = function (props) {
const { info } = { ...props };
console.log(`子组件接收: ${info.age}`);
return <div>显示子组件</div>;
};
export default () => {
const [age, setAge] = useState(6);
const [sex, setSex] = useState('boy');
const info = useMemo(() => {
return { name: 'echo', age: age };
}, [age]);
return (
<div>
<button onClick={() => { setAge(age => age + 1) }}>年龄+1</button>
<button onClick={() => { setSex(sex => sex === 'boy' ? 'girl' : sex) }}>改变性别</button><br />
<div>
{ `姓名:${info.name} 年龄:${info.age} 性别:${sex} `}
</div>
<Child info={info}></Child>
</div>
);
};
5. 使用useCallback避免函数重复创建
useCallback返回函数的memoized版本,防止每次渲染时重新创建函数,特别适用于传递给子组件的回调:
import React, { useState, useCallback, memo } from "react";
const Child = memo(function ({ onClick }) {
console.log("子组件渲染");
return <button onClick={onClick}>子组件</button>;
});
export default function Count() {
const [name, setName] = useState(0);
const [number, setNumber] = useState(0);
const addClick = useCallback(() => {
setNumber(number + 1);
}, []);
console.log("父组件渲染");
return (
<>
<button onClick={() => setName(name + 1)}>父组件</button>
<Child onClick={addClick} />
</>
);
}
6. 及时清理路由栈
在具有多个页面的应用中,使用Expo Router的dismissAll方法清理路由栈,可以避免路由循环和内存泄漏。例如,在切换到标签页时清理历史:
import { router } from 'expo-router';
function getScreen(screenId) {
const tabs = ['home', 'topUp', 'order', 'me', 'login', 'profile', 'businessAccount'];
if (tabs.includes(screenId)) {
router.dismissAll();
}
}
7. 常驻页面只缓存数据,不缓存DOM
基于Expo框架,可以在页面离开时销毁DOM,仅保留数据,以减少内存占用。通过useFocusEffect控制DOM可见性:
const [isDomVisible, setIsDomVisible] = useState(true);
useFocusEffect(
useCallback(() => {
setIsDomVisible(true);
return () => {
setIsDomVisible(false);
};
}, []),
);
return isDomVisible ? <GestureHandlerRootView /> : <Loading isLoading={true} />;
8. 使用FlatList虚拟列表处理长数据
对于长列表,应使用FlatList替代ScrollView,实现虚拟滚动以提升性能。例如,在订单列表中:
import IGFlatList from '@/CustomElements/IGFlatList';
<IGFlatList
data={orders}
loading={loading}
hasMoreData={hasMoreData}
handleLoadMore={handleLoadMore}
renderItem={ListItem}
keyExtractor={(item) => item.orderNo.toString()}
scrollToTopVar={scrollToTopVar}
/>
9. 特殊滑动需求使用BottomSheetFlatList
当列表需要与底部抽屉结合时,例如商店列表,使用BottomSheetFlatList:
import BottomSheet, { BottomSheetFlatList } from '@gorhom/bottom-sheet';
<BottomSheetFlatList
data={storesData}
keyExtractor={(item) => item.staPkId.toString()}
renderItem={renderItem}
/>
10. 全局状态持久化
使用redux-persist与AsyncStorage结合,实现Redux状态的持久化存储,确保应用重启后状态恢复:
import { persistStore, persistReducer } from 'redux-persist';
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE', 'persist/PAUSE', 'persist/PURGE', 'persist/REGISTER'],
},
}),
});
11. 接口缓存设置
利用AsyncStorage缓存常用数据,如主题和连锁店列表,减少网络请求,提升加载速度:
export const setInitStores = async (value) => {
try {
await AsyncStorage.setItem('initStores', JSON.stringify(value));
} catch (error) {
console.error('Error setting initStores:', error);
}
};
12. 全局入口优化
在应用的全局入口文件(如_layout.tsx)中,应严格控制重新渲染和IO请求,避免影响所有页面性能。通过useCallback和useEffect组织初始化逻辑:
const globalRequest = useCallback(async () => {
try {
dispatch(userActions.updateOprPkId({ oprPkId }));
const localTheme = await storageGetTheme();
if (localTheme) {
setTheme(handleTheme(localTheme));
setThemeReady(true);
} else {
setIsLoading(true);
}
const [oprInfoRes, themeRes] = await Promise.all([getOperInfo(), getTheme()]);
const { user } = store.getState();
if (user.token) {
const [accountRes] = await Promise.all([getAccountBaseInfo()]);
const accountInfo = accountRes.data.data[0];
dispatch(userActions.updateAccountInfo({ accountInfo }));
}
const oprInfo = oprInfoRes.data.data[0];
const themeData = themeRes.data.data[0];
setTheme(handleTheme(themeData));
setThemeReady(true);
storageSetTheme(themeData);
dispatch(userActions.SET_THEME(themeData));
dispatch(userActions.updateOprInfo({ oprInfo }));
if (oprInfo.currency) {
dispatch(i18nActions.updateCurrency({ currency: oprInfo.currency }));
}
if (oprInfo.currencySymbol) {
dispatch(
i18nActions.updateCurrencySymbol({
currencySymbol: oprInfo.currencySymbol,
}),
);
}
} catch (error) {
console.error('Error globalRequest', error);
ToastManager.show(t('networkError'));
setTheme(handleTheme(await storageGetTheme()));
} finally {
setIsLoading(false);
setThemeReady(true);
}
}, []);
useEffect(() => {
globalRequest();
}, []);
通过综合应用以上技巧,开发者可以有效提升React Native应用的性能,减少不必要的渲染和内存占用,确保用户体验流畅且高效。