一、全局状态redux优化,避免相同值,引起常驻页面重复渲染
const isAuthenticatedSelector = createSelector(
(state) => state.user,
(user) => user.isAuthenticated,
);
const isAuthenticated = useSelector(isAuthenticatedSelector);
二、合并多个dispatch与setState,React框架会合并成一次渲染
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,
}),
);
}
三、React.memo仅检查自己组件props变更,针对子组件不采用父组件的参数时,可减少子组件重复渲染
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 count={count}/> */}
<ChildMemo/>
</>
)
}
四、useMemo仅在依赖项改变时才重新计算memoized值,避免每次渲染都进行高开销的计算,类似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></br>
<div>
{ `姓名:${info.name} 年龄:${info.age} 性别:${sex} `}
</div>
<Child info={info}></Child>
</div>
)
}
五、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} />
</>
);
}
六、dismissAll清理路由栈
我们有61个页面,其中4个常驻页面,57个非常驻页面,及时清理路由栈,避免路由循环,以及利用js回收机制回收内存。
import { router } from 'expo-router';
function getScreen(screenId) {
const tabs = ['home', 'topUp', 'order', 'me', 'login', 'profile', 'businessAccount'];
if (tabs.includes(screenId)) {
router.dismissAll();
}
}
七、常驻页面只缓存数据,不缓存dom
基于expo框架缓存常驻页面,离开页面时销毁dom,只保留数据。
const [isDomVisible, setIsDomVisible] = useState(true);
useFocusEffect(
useCallback(() => {
setIsDomVisible(true);
return () => {
setIsDomVisible(false);
};
}, []),
);
return isDomVisible ? <GestureHandlerRootView /> : <Loading isLoading={true} />;
八、使用FlatList虚拟列表,避免长列表性能问题
import IGFlatList from '@/CustomElements/IGFlatList';
<IGFlatList
data={orders}
loading={loading}
hasMoreData={hasMoreData}
handleLoadMore={handleLoadMore}
renderItem={ListItem}
keyExtractor={(item) => item.orderNo.toString()}
scrollToTopVar={scrollToTopVar}
/>
九、首页Stores列表,有特殊滑动需求,需使用BottomSheetFlatList虚拟列表
import BottomSheet, { BottomSheetFlatList } from '@gorhom/bottom-sheet';
<BottomSheetFlatList
data={storesData}
keyExtractor={(item) => item.staPkId.toString()}
renderItem={renderItem}
/>
十、全局缓存设置,利用redux-persist实现全局状态持久化
使用redux-persist与AsyncStorage结合,每次状态变更存储在本地(需控制数据量),下次打开app优先使用本地缓存
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'],
},
}),
});
十一、接口缓存设置
利用AsyncStorage缓存主题、连锁店列表等数据,下次打开app优先使用本地缓存
export const setInitStores = async (value) => {
try {
await AsyncStorage.setItem('initStores', JSON.stringify(value));
} catch (error) {
console.error('Error setting initStores:', error);
}
};
十二、全局入口优化
严格控制全局路口_layout.tsx与tabs下_layout.tsx文件的重新渲染与io请求,避免所有页面可能引起的性能问题。
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();
}, []);