React Native 鸿蒙版:Calendar 日历组件完全指南
本文详细介绍如何在 OpenHarmony 6.0.0 (API 20) 平台上使用 React Native 实现功能完整的日历组件,包括日期选择、事件标记、本地化配置等核心功能,并提供完整的适配要点和性能优化建议。
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
一、项目背景
1.1 为什么需要 Calendar 组件?
日历组件是移动应用中的高频组件,广泛应用于:
- 日程管理应用
- 预约系统
- 考勤打卡
- 数据统计
- 事件提醒
1.2 OpenHarmony 平台挑战
┌─────────────────────────────────────────────────────────┐
│ OpenHarmony 日历组件面临的技术挑战 │
├─────────────────────────────────────────────────────────┤
│ │
│ 渲染引擎差异 与标准 ECMAScript 规范存在细微差异 │
│ ↓ 日期格式化 API 与 JavaScript 的 Intl API │
│ 日期处理 不完全兼容 │
│ ↓ 需要桥接 React Native 与 OpenHarmony │
│ 国际化机制 国际化方案 │
│ ↓ 可能影响日历组件的性能和交互体验 │
│ JS Bridge 通信 │
│ │
└─────────────────────────────────────────────────────────┘
1.3 核心依赖
bash
# 核心包
npm install react-native@0.72.5
# 鸿蒙适配包(关键)
npm install @react-native-oh/react-native-harmony@^0.72.108
# 可选:日历组件库
npm install react-native-calendars
二、架构设计
2.1 React Native 在 OpenHarmony 上的执行流程
┌─────────────────────────────────────────────────────────┐
│ React Native → OpenHarmony │
├─────────────────────────────────────────────────────────┤
│ │
│ JavaScript 代码 │
│ ↓ │
│ ┌─────────────┐ │
│ │ Metro 打包 │ JS Bundle │
│ └─────────────┘ │
│ ↓ │
│ ┌─────────────┐ │
│ │ JSI 接口 │ 高效通信 │
│ │ (替代 Bridge)│ 同步调用 │
│ └─────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────┐ │
│ │ React Native Core Modules │ │
│ │ • 日期处理 │ │
│ │ • 布局计算 │ │
│ │ • 事件分发 │ │
│ └─────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────┐ │
│ │ OpenHarmony 原生模块 │ │
│ │ • UI 渲染 │ │
│ │ • 平台 API 调用 │ │
│ └─────────────────────────────────────┘ │
│ ↓ │
│ 最终渲染 │
│ │
└─────────────────────────────────────────────────────────┘
2.2 日期处理适配要点
| 问题类型 | 标准实现 | OpenHarmony 适配方案 |
|---|---|---|
| 时区处理 | 基于 UTC 时间 | 显式指定时区,避免依赖系统默认 |
| 日期格式化 | Intl.DateTimeFormat |
使用 @ohos.intl 模块替代 |
| 夏令时转换 | 自动处理 | 处理边界情况,避免计算错误 |
三、类型定义与数据结构
3.1 核心类型定义
typescript
// src/types/calendar.ts
/**
* 日历日期数据模型
*/
export interface CalendarDay {
/** 日期数字 */
day: number;
/** 是否属于当前月份 */
isCurrentMonth: boolean;
/** 是否是今天 */
isToday: boolean;
/** 日期字符串 (YYYY-MM-DD) */
dateString?: string;
}
/**
* 日历组件配置
*/
export interface CalendarConfig {
/** 当前显示的日期 */
currentDate: Date;
/** 选中的日期字符串 */
selectedDate?: string;
/** 最小可选日期 */
minDate?: Date;
/** 最大可选日期 */
maxDate?: Date;
/** 首日是星期几 (0-6, 0=周日) */
firstDayOfWeek?: number;
/** 星期标题 */
weekDays?: string[];
/** 月份名称 */
monthNames?: string[];
}
/**
* 日历事件
*/
export interface CalendarEvent {
/** 日期字符串 */
date: string;
/** 事件类型 */
type: 'dot' | 'custom';
/** 颜色 */
color?: string;
/** 自定义内容 */
customComponent?: React.ReactNode;
}
/**
* 日历组件回调
*/
export interface CalendarCallbacks {
/** 日期点击事件 */
onDayPress?: (date: Date, dateString: string) => void;
/** 月份变化事件 */
onMonthChange?: (date: Date) => void;
/** 日期长按事件 */
onDayLongPress?: (date: Date, dateString: string) => void;
}
3.2 常量定义
typescript
// src/constants/calendar.ts
/**
* 星期标题(默认周日开始)
*/
export const DEFAULT_WEEK_DAYS = ['日', '一', '二', '三', '四', '五', '六'] as const;
/**
* 月份名称
*/
export const DEFAULT_MONTH_NAMES = [
'一月', '二月', '三月', '四月', '五月', '六月',
'七月', '八月', '九月', '十月', '十一月', '十二月'
] as const;
/**
* 日历常量
*/
export const CALENDAR_CONSTANTS = {
/** 每周天数 */
DAYS_PER_WEEK: 7,
/** 日历行数 */
CALENDAR_ROWS: 6,
/** 总天数 (6行 x 7天) */
TOTAL_DAYS: 42,
} as const;
四、核心组件实现
4.1 日历工具类
typescript
// src/utils/CalendarUtils.ts
import type { CalendarDay } from '../types/calendar';
/**
* 日历工具类
* 负责日期计算和数据处理
*/
export class CalendarUtils {
/**
* 获取月份的所有天数数据
* @param date 目标日期
* @param firstDayOfWeek 首日是星期几 (0-6)
* @returns 日历天数数组
*/
static getMonthData(date: Date, firstDayOfWeek: number = 0): CalendarDay[] {
const year = date.getFullYear();
const month = date.getMonth();
const today = new Date();
// 获取当月第一天和最后一天
const firstDayOfMonth = new Date(year, month, 1);
const lastDayOfMonth = new Date(year, month + 1, 0);
const days: CalendarDay[] = [];
const firstDayIndex = firstDayOfMonth.getDay();
// 填充上个月的日期
const prevMonthLastDay = new Date(year, month, 0).getDate();
for (let i = firstDayIndex - 1; i >= 0; i--) {
days.push({
day: prevMonthLastDay - i,
isCurrentMonth: false,
isToday: false,
});
}
// 填充当月日期
for (let i = 1; i <= lastDayOfMonth.getDate(); i++) {
const isToday = (
i === today.getDate() &&
month === today.getMonth() &&
year === today.getFullYear()
);
days.push({
day: i,
isCurrentMonth: true,
isToday,
dateString: this.formatDateString(year, month, i),
});
}
// 填充下个月的日期(补齐到42天)
const remainingDays = CALENDAR_CONSTANTS.TOTAL_DAYS - days.length;
for (let i = 1; i <= remainingDays; i++) {
days.push({
day: i,
isCurrentMonth: false,
isToday: false,
});
}
return days;
}
/**
* 格式化日期为 YYYY-MM-DD 字符串
*/
static formatDateString(year: number, month: number, day: number): string {
return `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
}
/**
* 解析日期字符串为 Date 对象
*/
static parseDateString(dateString: string): Date | null {
try {
const [year, month, day] = dateString.split('-').map(Number);
return new Date(year, month - 1, day);
} catch {
return null;
}
}
/**
* 检查两个日期是否是同一天
*/
static isSameDay(date1: Date, date2: Date): boolean {
return (
date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate()
);
}
/**
* 获取月份的第一天
*/
static getFirstDayOfMonth(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), 1);
}
/**
* 获取月份的最后一天
*/
static getLastDayOfMonth(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth() + 1, 0);
}
/**
* 添加月份
*/
static addMonths(date: Date, months: number): Date {
const newDate = new Date(date);
newDate.setMonth(newDate.getMonth() + months);
return newDate;
}
/**
* 检查日期是否在范围内
*/
static isDateInRange(date: Date, minDate?: Date, maxDate?: Date): boolean {
if (minDate && date < minDate) return false;
if (maxDate && date > maxDate) return false;
return true;
}
}
// 导入常量
const CALENDAR_CONSTANTS = {
DAYS_PER_WEEK: 7,
CALENDAR_ROWS: 6,
TOTAL_DAYS: 42,
} as const;
4.2 日历单元格组件
typescript
// src/components/CalendarDayCell.tsx
import React, { memo } from 'react';
import { TouchableOpacity, Text, View, StyleSheet } from 'react-native';
import type { CalendarDay } from '../types/calendar';
interface CalendarDayCellProps {
/** 日期数据 */
day: CalendarDay;
/** 是否被选中 */
isSelected: boolean;
/** 点击事件 */
onPress: () => void;
/** 自定义样式 */
customStyles?: {
cell?: object;
text?: object;
};
}
/**
* 日历单元格组件
* 使用 memo 优化渲染性能
*/
export const CalendarDayCell = memo<CalendarDayCellProps>(({
day,
isSelected,
onPress,
customStyles,
}) => {
const cellStyle = [
styles.cell,
!day.isCurrentMonth && styles.cellDisabled,
day.isToday && !isSelected && styles.cellToday,
isSelected && styles.cellSelected,
customStyles?.cell,
];
const textStyle = [
styles.text,
!day.isCurrentMonth && styles.textDisabled,
day.isToday && styles.textToday,
isSelected && styles.textSelected,
customStyles?.text,
];
return (
<TouchableOpacity
style={cellStyle}
onPress={onPress}
disabled={!day.isCurrentMonth}
activeOpacity={0.7}
>
<Text style={textStyle}>{day.day}</Text>
{/* 今天标记(未选中时显示) */}
{day.isToday && !isSelected && (
<View style={styles.todayDot} />
)}
</TouchableOpacity>
);
}, (prevProps, nextProps) => {
// 自定义比较函数
return (
prevProps.day.day === nextProps.day.day &&
prevProps.day.isCurrentMonth === nextProps.day.isCurrentMonth &&
prevProps.isSelected === nextProps.isSelected
);
});
const styles = StyleSheet.create({
cell: {
width: '100%',
aspectRatio: 1,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 8,
marginVertical: 2,
marginHorizontal: 2,
},
cellDisabled: {
opacity: 0.3,
},
cellToday: {
borderWidth: 1,
borderColor: '#007AFF',
},
cellSelected: {
backgroundColor: '#007AFF',
},
text: {
fontSize: 16,
color: '#333',
},
textDisabled: {
color: '#999',
},
textToday: {
color: '#007AFF',
fontWeight: '600',
},
textSelected: {
color: '#FFFFFF',
fontWeight: '600',
},
todayDot: {
position: 'absolute',
bottom: 4,
width: 4,
height: 4,
borderRadius: 2,
backgroundColor: '#007AFF',
},
});
4.3 日历主组件
typescript
// src/components/Calendar.tsx
import React, {
useState,
useCallback,
useMemo,
useRef,
useEffect,
} from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
TouchableOpacity,
Dimensions,
Platform,
} from 'react-native';
import { CalendarUtils } from '../utils/CalendarUtils';
import { CalendarDayCell } from './CalendarDayCell';
import type { CalendarConfig, CalendarCallbacks, CalendarEvent } from '../types/calendar';
import { DEFAULT_WEEK_DAYS, DEFAULT_MONTH_NAMES } from '../constants/calendar';
interface CalendarProps extends Partial<CalendarConfig>, CalendarCallbacks {
/** 事件标记 */
markedDates?: Record<string, CalendarEvent>;
/** 自定义样式 */
customStyles?: {
container?: object;
header?: object;
weekDay?: object;
};
/** 是否显示星期标题 */
showWeekDays?: boolean;
}
/**
* 日历组件
* 完整的日历功能实现
*/
export const Calendar: React.FC<CalendarProps> = ({
currentDate: initialDate = new Date(),
selectedDate: initialSelectedDate,
minDate,
maxDate,
firstDayOfWeek = 0,
weekDays = DEFAULT_WEEK_DAYS,
monthNames = DEFAULT_MONTH_NAMES,
markedDates = {},
onDayPress,
onMonthChange,
customStyles,
showWeekDays = true,
}) => {
// ==================== 状态管理 ====================
const [currentDate, setCurrentDate] = useState<Date>(initialDate);
const [selectedDate, setSelectedDate] = useState<string | undefined>(initialSelectedDate);
const [currentMonthData, setCurrentMonthData] = useState(() =>
CalendarUtils.getMonthData(currentDate, firstDayOfWeek)
);
const screenWidth = Dimensions.get('window').width;
// ==================== 月份导航 ====================
const changeMonth = useCallback((offset: number) => {
setCurrentDate((prev) => {
const newDate = CalendarUtils.addMonths(prev, offset);
// 触发月份变化回调
if (onMonthChange) {
onMonthChange(newDate);
}
return newDate;
});
}, [onMonthChange]);
// ==================== 日期选择 ====================
const handleDayPress = useCallback((day: CalendarDay) => {
if (!day.isCurrentMonth || !day.dateString) {
return;
}
// 检查日期范围
const date = CalendarUtils.parseDateString(day.dateString);
if (date && !CalendarUtils.isDateInRange(date, minDate, maxDate)) {
return;
}
setSelectedDate(day.dateString);
// 触发日期点击回调
if (onDayPress && date) {
onDayPress(date, day.dateString);
}
}, [onDayPress, minDate, maxDate]);
// ==================== 日期数据更新 ====================
useEffect(() => {
setCurrentMonthData(CalendarUtils.getMonthData(currentDate, firstDayOfWeek));
}, [currentDate, firstDayOfWeek]);
// ==================== 渲染函数 ====================
/**
* 渲染星期标题行
*/
const renderWeekHeader = useCallback(() => {
if (!showWeekDays) return null;
return (
<View style={[styles.weekHeader, customStyles?.weekDay]}>
{weekDays.map((day, index) => (
<View key={index} style={styles.weekDayCell}>
<Text style={styles.weekDayText}>{day}</Text>
</View>
))}
</View>
);
}, [weekDays, showWeekDays, customStyles]);
/**
* 渲染日历网格
*/
const renderCalendarGrid = useCallback(() => {
const rows = [];
const { DAYS_PER_WEEK } = require('../constants/calendar').CALENDAR_CONSTANTS;
for (let i = 0; i < 6; i++) {
const rowDays = currentMonthData.slice(i * DAYS_PER_WEEK, (i + 1) * DAYS_PER_WEEK);
rows.push(
<View key={i} style={styles.weekRow}>
{rowDays.map((day, index) => {
const isSelected = selectedDate === day.dateString;
const hasEvent = markedDates[day.dateString || ''];
return (
<View key={index} style={styles.dayCellWrapper}>
<CalendarDayCell
day={day}
isSelected={isSelected}
onPress={() => handleDayPress(day)}
/>
{/* 事件标记 */}
{hasEvent && hasEvent.type === 'dot' && (
<View
style={[
styles.eventDot,
{ backgroundColor: hasEvent.color || '#FF3B30' },
]}
/>
)}
</View>
);
})}
</View>
);
}
return rows;
}, [currentMonthData, selectedDate, markedDates, handleDayPress]);
// ==================== 主渲染 ====================
return (
<ScrollView style={[styles.container, customStyles?.container]}>
{/* 月份导航 */}
<View style={[styles.monthNavigation, customStyles?.header]}>
<TouchableOpacity
style={styles.navButton}
onPress={() => changeMonth(-1)}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<Text style={styles.navButtonText}>‹</Text>
</TouchableOpacity>
<Text style={styles.monthText}>
{currentDate.getFullYear()}年 {monthNames[currentDate.getMonth()]}
</Text>
<TouchableOpacity
style={styles.navButton}
onPress={() => changeMonth(1)}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<Text style={styles.navButtonText}>›</Text>
</TouchableOpacity>
</View>
{/* 星期标题 */}
{renderWeekHeader()}
{/* 日历网格 */}
<View style={styles.daysContainer}>
{renderCalendarGrid()}
</View>
{/* 选中日期信息 */}
{selectedDate && (
<View style={styles.selectedInfo}>
<Text style={styles.selectedInfoText}>
已选择: {selectedDate}
</Text>
</View>
)}
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFFFFF',
},
monthNavigation: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 16,
paddingHorizontal: 20,
},
navButton: {
padding: 8,
},
navButtonText: {
fontSize: 24,
color: '#007AFF',
fontWeight: '300',
},
monthText: {
fontSize: 18,
fontWeight: '600',
color: '#333',
},
weekHeader: {
flexDirection: 'row',
paddingBottom: 8,
borderBottomWidth: 1,
borderBottomColor: '#E5E5E5',
marginBottom: 8,
},
weekDayCell: {
flex: 1,
alignItems: 'center',
},
weekDayText: {
fontSize: 14,
fontWeight: '500',
color: '#666',
},
daysContainer: {
paddingHorizontal: 8,
},
weekRow: {
flexDirection: 'row',
},
dayCellWrapper: {
flex: 1,
alignItems: 'center',
},
eventDot: {
position: 'absolute',
bottom: 4,
width: 6,
height: 6,
borderRadius: 3,
},
selectedInfo: {
alignItems: 'center',
paddingVertical: 16,
borderTopWidth: 1,
borderTopColor: '#E5E5E5',
marginTop: 8,
},
selectedInfoText: {
fontSize: 16,
color: '#007AFF',
fontWeight: '500',
},
});
五、使用示例
5.1 基础用法
typescript
// App.tsx
import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { Calendar } from './src/components/Calendar';
export default function App() {
const handleDayPress = (date: Date, dateString: string) => {
console.log('Selected:', dateString);
};
const handleMonthChange = (date: Date) => {
console.log('Month changed to:', date.toISOString());
};
return (
<View style={styles.container}>
<Calendar
currentDate={new Date()}
onDayPress={handleDayPress}
onMonthChange={handleMonthChange}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
});
5.2 带事件标记的日历
typescript
// EventCalendarExample.tsx
import React, { useState } from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { Calendar } from './src/components/Calendar';
import type { CalendarEvent } from './src/types/calendar';
export default function EventCalendarExample() {
const [markedDates, setMarkedDates] = useState<Record<string, CalendarEvent>>({
[new Date().toISOString().split('T')[0]]: {
type: 'dot',
color: '#FF3B30',
},
// 添加更多事件标记...
});
return (
<View style={styles.container}>
<Text style={styles.title}>事件日历示例</Text>
<Calendar
currentDate={new Date()}
markedDates={markedDates}
onDayPress={(date, dateString) => {
console.log('Selected:', dateString);
}}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
paddingTop: 60,
},
title: {
fontSize: 20,
fontWeight: '600',
textAlign: 'center',
marginBottom: 16,
},
});
六、OpenHarmony 适配要点
6.1 平台检测与适配
typescript
// src/utils/PlatformAdapter.ts
import { Platform } from 'react-native';
/**
* 平台适配工具类
*/
export class PlatformAdapter {
/**
* 检测是否运行在 OpenHarmony 平台
*/
static isOpenHarmony(): boolean {
return Platform.OS === 'harmony';
}
/**
* 获取平台特定的日期格式化配置
*/
static getDateFormatConfig() {
if (this.isOpenHarmony()) {
return {
locale: 'zh-CN',
options: {
year: 'numeric',
month: 'long',
day: 'numeric',
},
};
}
// 其他平台的默认配置
return {
locale: 'en-US',
options: {
year: 'numeric',
month: 'short',
day: 'numeric',
},
};
}
/**
* 获取本地化的星期标题
*/
static getLocalizedWeekDays(): string[] {
if (this.isOpenHarmony()) {
// OpenHarmony 使用本地化数据
return ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
}
return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
}
/**
* 获取本地化的月份名称
*/
static getLocalizedMonthNames(): string[] {
if (this.isOpenHarmony()) {
return [
'一月', '二月', '三月', '四月', '五月', '六月',
'七月', '八月', '九月', '十月', '十一月', '十二月'
];
}
return [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
];
}
}
6.2 适配清单
| 适配项 | OpenHarmony 特殊处理 | 推荐方案 |
|---|---|---|
| 本地化 | @ohos.intl 模块 |
手动配置本地化数据 |
| 日期格式化 | 不完全兼容 Intl |
使用自定义格式化函数 |
| Flexbox | 嵌套容器有差异 | 避免深层嵌套,测试验证 |
| 触摸事件 | hitSlop 处理 |
增加触摸区域 |
| 性能优化 | 渲染引擎差异 | 使用 memo 和 useCallback |
七、性能优化
7.1 优化策略
┌─────────────────────────────────────────────────────────┐
│ 日历组件性能优化策略 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. 组件级优化 │
│ • React.memo 包装日期单元格 │
│ • useCallback 缓存事件处理函数 │
│ • useMemo 缓存计算结果 │
│ │
│ 2. 数据优化 │
│ • 避免不必要的重新计算 │
│ • 使用原始数据类型 │
│ • 减少对象创建 │
│ │
│ 3. 渲染优化 │
│ • 避免深层嵌套 │
│ • 使用绝对定位而非嵌套 Flex │
│ • 移除不必要的透明度变化 │
│ │
│ 4. 内存优化 │
│ • 懒加载非关键功能 │
│ • 及时清理订阅和定时器 │
│ │
└─────────────────────────────────────────────────────────┘
7.2 性能优化代码示例
typescript
// src/components/OptimizedCalendar.tsx
import React, { useMemo, useCallback, memo } from 'react';
import { View } from 'react-native';
/**
* 优化的日历行组件
*/
export const CalendarRow = memo(({ days, onDayPress }) => {
// 缓存渲染的单元格
const cells = useMemo(() => {
return days.map((day, index) => (
<CalendarDayCell
key={`${day.dateString}-${day.isCurrentMonth}`}
day={day}
isSelected={false}
onPress={() => onDayPress(day)}
/>
));
}, [days, onDayPress]);
return (
<View style={styles.row}>
{cells}
</View>
);
});
/**
* 性能监控 Hook
*/
export function usePerformanceMonitor(componentName: string) {
if (__DEV__) {
const renderCount = useRef(0);
const lastRenderTime = useRef(Date.now());
useEffect(() => {
renderCount.current += 1;
const currentTime = Date.now();
const timeSinceLastRender = currentTime - lastRenderTime.current;
console.log(
`[Performance] ${componentName} render #${renderCount.current}, ` +
`time since last: ${timeSinceLastRender}ms`
);
lastRenderTime.current = currentTime;
});
}
}
八、完整示例应用
typescript
// CalendarDemoApp.tsx
/**
* 日历组件完整演示应用
* 展示所有核心功能和适配要点
*/
import React, { useState, useCallback } from 'react';
import {
SafeAreaView,
ScrollView,
StyleSheet,
View,
Text,
TouchableOpacity,
Alert,
} from 'react-native';
import { Calendar } from './src/components/Calendar';
import { PlatformAdapter } from './src/utils/PlatformAdapter';
import type { CalendarEvent } from './src/types/calendar';
export default function CalendarDemoApp() {
const [selectedDate, setSelectedDate] = useState<string>('');
const [markedDates, setMarkedDates] = useState<Record<string, CalendarEvent>>({});
// 获取本地化配置
const weekDays = PlatformAdapter.getLocalizedWeekDays();
const monthNames = PlatformAdapter.getLocalizedMonthNames();
const handleDayPress = useCallback((date: Date, dateString: string) => {
setSelectedDate(dateString);
Alert.alert('日期选择', `您选择了: ${dateString}`);
}, []);
const handleAddEvent = useCallback(() => {
if (!selectedDate) {
Alert.alert('提示', '请先选择一个日期');
return;
}
setMarkedDates(prev => ({
...prev,
[selectedDate]: {
type: 'dot',
color: '#FF3B30',
},
}));
Alert.alert('成功', `已为 ${selectedDate} 添加事件标记`);
}, [selectedDate]);
return (
<SafeAreaView style={styles.container}>
{/* 平台信息 */}
<View style={styles.platformBanner}>
<Text style={styles.platformText}>
{PlatformAdapter.isOpenHarmony() ? 'OpenHarmony' : 'React Native'} 日历组件
</Text>
</View>
{/* 日历组件 */}
<Calendar
currentDate={new Date()}
selectedDate={selectedDate}
weekDays={weekDays}
monthNames={monthNames}
markedDates={markedDates}
onDayPress={handleDayPress}
/>
{/* 操作按钮 */}
<View style={styles.actions}>
<TouchableOpacity
style={styles.button}
onPress={handleAddEvent}
>
<Text style={styles.buttonText}>添加事件</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.buttonSecondary]}
onPress={() => setSelectedDate('')}
>
<Text style={styles.buttonText}>清除选择</Text>
</TouchableOpacity>
</View>
{/* 功能说明 */}
<View style={styles.features}>
<Text style={styles.featuresTitle}>核心功能</Text>
<FeatureItem text="日期选择 - 点击日期进行选择" />
<FeatureItem text="月份导航 - 左右切换月份" />
<FeatureItem text="事件标记 - 红点标记有事件的日期" />
<FeatureItem text="本地化 - 自动适配平台语言" />
</View>
</SafeAreaView>
);
}
function FeatureItem({ text }: { text: string }) {
return (
<View style={styles.featureItem}>
<Text style={styles.featureBullet}>•</Text>
<Text style={styles.featureText}>{text}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
platformBanner: {
backgroundColor: '#007AFF',
paddingVertical: 12,
paddingHorizontal: 20,
},
platformText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
textAlign: 'center',
},
actions: {
flexDirection: 'row',
justifyContent: 'space-around',
padding: 20,
},
button: {
backgroundColor: '#007AFF',
paddingHorizontal: 32,
paddingVertical: 12,
borderRadius: 8,
},
buttonSecondary: {
backgroundColor: '#8E8E93',
},
buttonText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
},
features: {
backgroundColor: '#FFFFFF',
margin: 20,
padding: 20,
borderRadius: 12,
},
featuresTitle: {
fontSize: 18,
fontWeight: '600',
marginBottom: 16,
},
featureItem: {
flexDirection: 'row',
marginBottom: 12,
},
featureBullet: {
fontSize: 20,
color: '#007AFF',
marginRight: 8,
},
featureText: {
fontSize: 14,
color: '#333',
},
});
九、总结
9.1 核心要点
| 主题 | 关键要点 |
|---|---|
| 架构设计 | JSI 替代 Bridge,提供更高效的通信机制 |
| 日期处理 | 使用工具类封装,确保 OpenHarmony 兼容性 |
| 本地化 | 手动配置本地化数据,避免依赖系统 API |
| 性能优化 | memo + useCallback + useMemo 减少重渲染 |
| 平台适配 | 平台检测 + 条件渲染处理差异 |
9.2 最佳实践
- 类型安全:使用 TypeScript 定义完整的类型系统
- 组件拆分:将日历组件拆分为可复用的子组件
- 性能优化:使用 React.memo 和 useCallback 优化渲染
- 错误处理:添加边界检查和错误恢复机制
- 文档完善:提供清晰的 API 文档和使用示例
9.3 下一步
- 实现范围选择功能
- 添加自定义日期渲染
- 支持多选日期
- 实现日程事件视图
- 添加动画效果
十、资源链接
- React Native 官方文档: https://reactnative.dev
- OpenHarmony 开发者文档: https://developer.huawei.com/consumer/cn/openharmony
- @react-native-oh/react-native-harmony: https://www.npmjs.com/package/@react-native-oh/react-native-harmony
- react-native-calendars: https://github.com/wix/react-native-calendars
结语:通过本文的详细介绍和代码实现,我们展示了如何在 OpenHarmony 平台上使用 React Native 构建功能完整的日历组件。关键在于理解平台差异、做好类型定义、优化性能,并遵循最佳实践。希望这份指南能帮助你在鸿蒙生态中开发出优秀的日历组件!
📕个人领域 :Linux/C++/java/AI🚀 个人主页 :有点流鼻涕 · CSDN
💬 座右铭 : "向光而行,沐光而生。"
