You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 10:57:03