使用react-native-calendars时打开react-native-modal卡顿问题求助
React Native Calendars CalendarList 导致Modal卡顿的解决方案
问题根源在于CalendarList默认会预渲染多个月份,再加上代码里几个触发频繁重渲染的点,导致打开Modal时主线程被占用,出现卡顿。以下是针对性的优化方案:
1. 缓存markedDates,避免不必要的重渲染
你现在每次渲染都会调用getSelectedDates()和getMarkedDates()生成新对象,CalendarList会认为markedDates props发生变化,触发全量重渲染。用useMemo缓存这两个结果:
// 替换原来的getSelectedDates和getMarkedDates const selectedDates = useMemo(() => ({ [moment().format('YYYY-MM-DD')]: { selected: true, disableTouchEvent: true, selectedColor: theme.colors.secondary, selectedTextColor: theme.colors.primary, marked: false, dotColor: theme.colors.primary, }, }), [theme.colors.secondary, theme.colors.primary]); const markedDates = useMemo(() => { const dates = ['2024-05-16', '2024-05-10', '2024-05-20']; let result = {}; dates.forEach(date => { result[date] = { selected: true, disableTouchEvent: true, selectedColor: '#fff', selectedTextColor: theme.colors.primary, marked: true, dotColor: theme.colors.primary, }; }); return result; }, [theme.colors.primary]);
然后在CalendarList里使用:
markedDates={{ ...selectedDates, ...markedDates, }}
2. 抽离并缓存自定义Day组件
原来的dayComponent是在render函数内部定义的,每次父组件渲染都会创建新的组件实例,导致所有日期格子重渲染。把它抽成独立组件并用React.memo包裹:
// 在Calendar组件外部定义 const CustomDay = React.memo(({ date, state, marking, onPress, theme }) => { const isMarked = marking?.marked || false; const styles = makeDayStyles(theme); return ( <TouchableOpacity onPress={onPress} style={[ styles.dayContainer, isMarked && styles.markedDayContainer, ]}> <Text style={[styles.dayText, isMarked && styles.markedDayText]}> {date?.day} </Text> </TouchableOpacity> ); }); // 单独的样式函数 const makeDayStyles = (theme) => StyleSheet.create({ dayContainer: { width: 40, height: 40, justifyContent: 'center', alignItems: 'center', borderRadius: 20, }, markedDayContainer: { backgroundColor: theme.colors.secondary, }, dayText: { color: '#000', fontFamily: theme.fontFamily.semiBold, }, markedDayText: { color: theme.colors.primary, }, });
然后在CalendarList里使用:
dayComponent={({ date, state, marking, onPress }) => ( <CustomDay date={date} state={state} marking={marking} onPress={onPress} theme={theme} /> )}
3. 减少CalendarList预渲染的月份数量
CalendarList默认会预渲染过去2个月和未来2个月(共4个月),可以通过pastScrollRange和futureScrollRange减少这个数量,降低初始渲染压力:
<CalendarList // 其他props... pastScrollRange={1} futureScrollRange={1} />
4. 清理不必要的日志
移除console.log(showBookedModal, 'showBookedModal'),频繁的日志输出也会占用主线程资源。
优化后的完整代码
import React, {useEffect, useState, useMemo} from 'react'; import { SafeAreaView, StatusBar, StyleSheet, Text, View, ActivityIndicator, TouchableOpacity, } from 'react-native'; import {CalendarList, CalendarProvider, Agenda} from 'react-native-calendars'; import {useTheme} from 'react-native-paper'; import Filter from '../../../assets/icons/Filter'; import LeftArrow from '../../../assets/icons/LeftArrow'; import Header from '../../../components/Header'; import BookedModal from '../../../components/calendars/BookedModal'; import OptionsModal from '../../../components/OptionsModal'; import {NavigationProps} from 'react-native-navigation'; import moment from 'moment'; import {ThemeProps} from '../../../utils/theme'; const CustomDay = React.memo(({ date, state, marking, onPress, theme }) => { const isMarked = marking?.marked || false; const styles = makeDayStyles(theme); return ( <TouchableOpacity onPress={onPress} style={[ styles.dayContainer, isMarked && styles.markedDayContainer, ]}> <Text style={[styles.dayText, isMarked && styles.markedDayText]}> {date?.day} </Text> </TouchableOpacity> ); }); const makeDayStyles = (theme) => StyleSheet.create({ dayContainer: { width: 40, height: 40, justifyContent: 'center', alignItems: 'center', borderRadius: 20, }, markedDayContainer: { backgroundColor: theme.colors.secondary, }, dayText: { color: '#000', fontFamily: theme.fontFamily.semiBold, }, markedDayText: { color: theme.colors.primary, }, }); const Calendar = (props: NavigationProps) => { const theme: ThemeProps = useTheme(); const styles = makeStyles(theme); const [selected, setSelected] = useState(moment().format('YYYY-MM-DD')); const [showOptionsModal, setshowOptionsModal] = useState(false); const [showBookedModal, setshowBookedModal] = useState(false); const [loading, setloading] = useState(false); const toggleShowBookedModal = () => setshowBookedModal(!showBookedModal); const toggleShowOptionsModal = () => setshowOptionsModal(!showOptionsModal); const selectedDates = useMemo(() => ({ [moment().format('YYYY-MM-DD')]: { selected: true, disableTouchEvent: true, selectedColor: theme.colors.secondary, selectedTextColor: theme.colors.primary, marked: false, dotColor: theme.colors.primary, }, }), [theme.colors.secondary, theme.colors.primary]); const markedDates = useMemo(() => { const dates = ['2024-05-16', '2024-05-10', '2024-05-20']; let result = {}; dates.forEach(date => { result[date] = { selected: true, disableTouchEvent: true, selectedColor: '#fff', selectedTextColor: theme.colors.primary, marked: true, dotColor: theme.colors.primary, }; }); return result; }, [theme.colors.primary]); return ( <SafeAreaView style={styles.screen}> <BookedModal componentId={props.componentId} visible={showBookedModal} onClose={toggleShowBookedModal} /> <Header> <View style={styles.headerContainer}> <View style={styles.row}> <LeftArrow /> <Text style={styles.headerTitle}>Calendar</Text> </View> <Filter onPress={toggleShowOptionsModal} /> </View> </Header> <View style={styles.mainContainer}> <OptionsModal onClose={toggleShowOptionsModal} options={[ {title: 'All', onPress: () => {}}, {title: 'Bookings', onPress: () => {}}, {title: 'Reminders', onPress: () => {}}, ]} visible={showOptionsModal} /> <CalendarList markedDates={{ ...selectedDates, ...markedDates, }} theme={{ backgroundColor: '#ffffff', textDisabledColor: '#d9e', textMonthfontFamily: theme.fontFamily.semiBold, monthTextColor: theme.colors.primary, textMonthFontSize: 20, todayTextColor: theme.colors.primary, }} dayComponent={({ date, state, marking, onPress }) => ( <CustomDay date={date} state={state} marking={marking} onPress={onPress} theme={theme} /> )} onDayPress={day => { console.log('ONPRESSED'); toggleShowBookedModal(); }} markingType="custom" pastScrollRange={1} futureScrollRange={1} /> </View> <StatusBar backgroundColor={theme.colors.primary} /> </SafeAreaView> ); }; export default Calendar; const makeStyles = (theme: ThemeProps) => StyleSheet.create({ screen: { flex: 1, backgroundColor: theme.colors.primary, }, mainContainer: { backgroundColor: '#F9F9F9', height: '100%', }, headerContainer: { flexDirection: 'row', alignItems: 'center', height: '100%', paddingHorizontal: 20, justifyContent: 'space-between', }, headerTitle: { fontFamily: theme.fontFamily.semiBold, fontSize: 18, color: '#fff', }, row: { flexDirection: 'row', gap: 10, }, });
内容的提问来源于stack exchange,提问作者Rooban
相关产品推荐
相关产品推荐

