React Native 鸿蒙实战:datetimepicker 日期时间选择器在 HarmonyOS 上的接入与使用

库版本:@react-native-oh-tpl/datetimepicker 9.0.0-beta.1(OpenHarmony 适配版)

上游依赖:@react-native-community/datetimepicker 9.1.0

适配仓库:https://atomgit.com/CPF-RN/rntpc_datetimepicker

验证环境:RNOH 0.86.1(对齐 React Native 0.86.3)

设备:鸿蒙 PC(OpenHarmony,2in1 形态)

在这里插入图片描述
在这里插入图片描述

一、环境搭建

React Native 鸿蒙环境搭建请参考官方文档:RNOH 环境搭建指南

本章不重复展开。搭建完成后,确认 pnpm --version 输出 10.x 以上,DevEco Studio 可正常创建鸿蒙工程即可。

二、应用背景

2.1 当前的应用场景与痛点

日期选择、时间选择是表单填写、预约下单、提醒设置等场景中的高频需求。React Native 在 Android 和 iOS 上通过 @react-native-community/datetimepicker 提供成熟的日期时间选择方案,但鸿蒙系统使用完全不同的 ArkUI DatePicker / TimePicker 组件体系,开发者如果自行适配,需要:

  • 对接鸿蒙 ArkUI 的 DatePicker、TimePicker、CalendarPicker 等原生组件,API 与 RN 的 JS 层模型不同;
  • 编写 ArkTS 原生 Fabric UI 组件桥接 JS 调用与系统日期选择器;
  • 处理日期格式转换、模式切换(date / time / datetime)、显示样式(spinner / inline / compact)等参数映射;
  • 配置 codegen spec、HAR 包编译、autolinking 注册等 RNOH 构建流程。

2.2 为什么需要这个库

@react-native-oh-tpl/datetimepicker 是 RNOH 社区基于 @react-native-community/datetimepicker 进行鸿蒙适配的三方库,在 OpenHarmony 平台上通过 ArkTS 重新实现了原生层,让 React Native 鸿蒙应用无需编写原生代码,即可在 JS 层以与 Android / iOS 一致的 API 完成日期时间选择。

2.3 解决什么问题

一句话总结:为 React Native 鸿蒙应用提供开箱即用的日期时间选择能力。具体包括:

  1. 多种日期选择模式(date 日期 / time 时间);
  2. 多种显示样式(spinner 滚轮 / inline 内联日历 / compact 紧凑 / default 自动选择);
  3. 日期范围限制(通过 minimumDate / maximumDate 控制可选范围);
  4. 24 小时制支持(is24Hour 参数);
  5. 选择结果实时回调(onChange 返回选中日期);
  6. 年份选择支持(startOnYearSelection 快速跳转年份)。

三、功能介绍

功能说明适用场景
日期选择mode=“date”,选择年月日生日、入职日期、截止日期
时间选择mode=“time”,选择时分闹钟、提醒、预约时间
内联日历display=“inline”,日历视图大屏设备、需要直观查看月份
紧凑日历display=“compact”,小型日历空间受限的表单区域
滚轮选择display=“spinner”,滚轮样式传统 iOS 风格交互
日期范围限制minimumDate / maximumDate限制可选日期区间
24 小时制is24Hour=true国际化时间显示
年份快选startOnYearSelection需要快速切换到特定年份

四、使用方法

4.1 引入三方库

在 RNOH 工程中接入该库需要完成两个配置:npm 依赖(本地引入)、HAR 包引用。该库支持 autolinking,无需手动注册 Package。

第一步:添加 npm 依赖

在 tester 的 package.json 的 dependencies 中添加:

{
  "dependencies": {
    "@react-native-community/datetimepicker": "9.1.0",
    "@react-native-oh-tpl/datetimepicker": "file:../../node_modules/@react-native-oh-tpl/datetimepicker"
  }
}

@react-native-community/datetimepicker 是上游 JS 层依赖,提供类型定义和工具函数;@react-native-oh-tpl/datetimepicker 是鸿蒙适配版的原生实现。

执行 pnpm install 拉取依赖。

第二步:添加 HAR 包引用

在 harmony/oh-package.json5 的 dependencies 中添加 HAR 文件引用:

{
  "dependencies": {
    "@react-native-ohos/datetimepicker": "file:../../../node_modules/@react-native-oh-tpl/datetimepicker/harmony/datetimepicker.har"
  }
}

注意 HAR 路径从 oh-package.json5 所在目录(harmony/)算起,回退三级到根 node_modules。路径写错会导致 ohpm 安装失败。

第三步:修复 HAR 打包缺陷(重要)

当前版本的 HAR 存在打包缺陷——缺少 DateTimePickerPackage.ets 文件,且 index.ets 是旧版本(缺少 export default DateTimePickerPackage),直接使用会导致 ArkTS 编译报错。解决方法是从源码目录重建 HAR。

先从 AtomGit 克隆适配仓库到 node_modules:

cd node_modules/@react-native-oh-tpl/datetimepicker/harmony
git clone https://atomgit.com/CPF-RN/rntpc_datetimepicker.git datetimepicker

然后执行项目根目录下的 scripts/rebuild-har.js 脚本:

node scripts/rebuild-har.js

该脚本会:

  1. 从克隆的源码目录完整重建 HAR 文件(tar.gz 格式);
  2. 验证所有关键文件存在(index.ets、DateTimePickerPackage.ets、RNDateTimePicker.ets、C++ 源码等);
  3. 验证 index.ets 包含正确的 export default DateTimePickerPackage;
  4. 自动清除 ohpm 缓存,确保下次 Sync 时重新解压。

执行完成后,运行 ohpm install 重新解压 HAR,然后 Clean Build 即可。

4.2 核心 API

该库导出一个 React 组件 DateTimePicker,通过 props 控制行为:

import DateTimePicker from '@react-native-oh-tpl/datetimepicker';

<DateTimePicker
  value={new Date()}           // 当前选中日期(必传)
  mode="date"                  // 模式:'date' | 'time'
  display="spinner"            // 样式:'default' | 'spinner' | 'compact' | 'inline'
  onChange={(event, date) => { // 选择变更回调
    if (date) setDate(date);
  }}
  minimumDate={new Date(2020, 0, 1)}  // 可选最小日期
  maximumDate={new Date(2030, 11, 31)} // 可选最大日期
  is24Hour={true}              // 24 小时制
  disabled={false}             // 是否禁用
/>

注意:display 参数在鸿蒙平台上会被映射为 displayIOS 传递给原生组件。当前 SDK 版本下,spinner 和 default 样式会使用 CalendarPicker 替代(详见 FAQ)。

4.3 完整示例代码

以下是在 RNOH tester 工程中验证通过的完整示例(DateTimePickerExample.tsx),展示四种模式的日期时间选择器:

import React, {useState} from 'react';
import {
  View,
  Text,
  StyleSheet,
  ScrollView,
  Platform,
} from 'react-native';
import DateTimePicker from '@react-native-oh-tpl/datetimepicker';

function DateTimePickerCard({
  title,
  mode,
  display,
}: {
  title: string;
  mode: 'date' | 'time';
  display: 'default' | 'spinner' | 'compact' | 'inline';
}) {
  const [date, setDate] = useState(new Date());

  const handleChange = (event: any, selectedDate?: Date) => {
    if (selectedDate) {
      setDate(selectedDate);
    }
  };

  return (
    <View style={styles.card}>
      <Text style={styles.cardTitle}>{title}</Text>
      <Text style={styles.cardValue}>
        {mode === 'date'
          ? date.toLocaleDateString()
          : date.toLocaleTimeString()}
      </Text>
      <View style={styles.pickerContainer}>
        <DateTimePicker
          value={date}
          mode={mode}
          display={display}
          onChange={handleChange}
          is24Hour={true}
          style={{ flex: 1 }}
        />
      </View>
    </View>
  );
}

export function DateTimePickerExample() {
  return (
    <ScrollView style={styles.container}>
      <Text style={styles.title}>DateTimePicker Demo</Text>
      <Text style={styles.subtitle}>
        Platform: {Platform.OS === 'harmony' ? 'HarmonyOS' : Platform.OS}
      </Text>

      <DateTimePickerCard
        title="Date Picker (spinner)"
        mode="date"
        display="spinner"
      />

      <DateTimePickerCard
        title="Date Picker (inline)"
        mode="date"
        display="inline"
      />

      <DateTimePickerCard
        title="Time Picker (spinner)"
        mode="time"
        display="spinner"
      />

      <DateTimePickerCard
        title="Date Picker (compact)"
        mode="date"
        display="compact"
      />
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {flex: 1, padding: 16, backgroundColor: '#F2F2F7'},
  title: {fontSize: 24, fontWeight: '700', marginBottom: 4, color: '#000'},
  subtitle: {fontSize: 14, color: '#666', marginBottom: 20},
  card: {
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
  },
  cardTitle: {fontSize: 16, fontWeight: '600', color: '#333', marginBottom: 4},
  cardValue: {fontSize: 14, color: '#007AFF', marginBottom: 12},
  pickerContainer: {
    height: 200,
    alignItems: 'center',
    justifyContent: 'center',
  },
});

运行效果:页面显示四张白色圆角卡片,分别展示 spinner 日期滚轮、inline 内联日历、spinner 时间滚轮和 compact 紧凑日历。选择日期后卡片内蓝色文字实时更新。

重要:DateTimePicker 是 Fabric 原生组件,必须通过 style={{ flex: 1 }} 或其他方式指定尺寸,否则 Yoga 布局引擎会计算为 0 高度导致组件不可见。

五、FAQ

5.1 常见问题

Q1:编译报 Module ‘…index’ has no default export

这是 HAR 打包缺陷导致的。原始 HAR 中 index.ets 是旧版本,只有 export * 导出,缺少 export default DateTimePickerPackage。autolinking 生成的 RNOHPackagesFactory.ets 使用了 import DateTimePickerPackage from ‘@react-native-ohos/datetimepicker’,要求 index.ets 有默认导出。

解决办法是从源码重建 HAR。正确的 index.ets 内容如下:

import { DateTimePickerPackage } from './src/main/ets/DateTimePickerPackage'
export default DateTimePickerPackage
export * from './src/main/ets/RNDateTimePicker'

同时 HAR 中必须包含 DateTimePickerPackage.ets 文件,该文件负责注册 RNDateTimePicker 组件构建器:

import { RNOHPackage, ComponentBuilderContext } from '@rnoh/react-native-openharmony';
import { RNDateTimePicker } from './RNDateTimePicker';
@Builder
function buildDateTimePicker(ctx: ComponentBuilderContext) {
    RNDateTimePicker({ ctx: ctx.rnComponentContext, tag: ctx.tag, })
}
export class DateTimePickerPackage extends RNOHPackage  {
  createWrappedCustomRNComponentBuilderByComponentNameMap(): Map<string, WrappedBuilder<[ComponentBuilderContext]>> {
    return new Map().set("RNDateTimePicker", wrapBuilder(buildDateTimePicker))
  }
}

执行 node scripts/rebuild-har.js 从源码目录完整重建 HAR 即可修复。

Q2:执行 rebuild-har.js 后 Sync,仍然报 no default export

ohpm 有缓存机制。即使 HAR 文件已更新,只要包名 + 版本哈希没变,ohpm 不会重新解压到 oh_modules,编译时读到的仍然是旧文件。

可以通过检查 oh_modules 中的 index.ets 来确认缓存是否生效:

# 如果输出没有 "export default DateTimePickerPackage",说明缓存未更新
cat harmony/oh_modules/@react-native-ohos/datetimepicker/index.ets

解决方法是手动清除 ohpm 缓存目录,然后重新安装:

# 删除 ohpm 缓存的解压目录
rm -rf harmony/oh_modules/.ohpm/@react-native-ohos+datetimepicker*
# 删除 ohpm 创建的链接目录
rm -rf harmony/oh_modules/@react-native-ohos/datetimepicker
# 重新安装
ohpm install

rebuild-har.js 脚本已内置自动清缓存步骤,正常情况下无需手动操作。

Q3:页面打开了 DateTimePicker Demo,但选择器区域空白,看不到组件

DateTimePicker 是 Fabric 原生组件,没有 intrinsic size(内在尺寸)。如果不在 style 中指定宽高,Yoga 布局引擎会将其计算为 0 高度,组件虽然已渲染但完全不可见。

错误写法(无尺寸):

<DateTimePicker
  value={date}
  mode="date"
  display="inline"
  onChange={handleChange}
/>

正确写法(通过 flex: 1 填满父容器):

<View style={{ height: 200 }}>
  <DateTimePicker
    value={date}
    mode="date"
    display="inline"
    onChange={handleChange}
    style={{ flex: 1 }}
  />
</View>

父容器必须有明确的高度(height: 200),DateTimePicker 通过 flex: 1 撑满该高度。如果父容器也没有固定高度,需要一路向上确保布局链有确定的尺寸约束。

Q4:inline 和 compact 模式正常显示,但 spinner 模式不渲染

当前 HarmonyOS SDK(targetSdkVersion 6.0.0(20))中,内置的 DatePicker 和 TimePicker 组件存在兼容性问题,无法正常渲染。inline 和 compact 模式使用的是库自带的 CalendarPicker 组件(ArkTS 自绘),所以不受影响。

问题出在 RNDateTimePicker.ets 的 build 方法中,spinner 模式使用了系统 DatePicker:

// 以下代码在当前 SDK 版本下不渲染
DatePicker({
  start: new Date(1970, 0, 0),
  end: new Date(2100, 0, 0),
  selected: this.selectDate
})
  .lunar(this.isLunar)
  .width("100%").height("100%")
  .onDateChange((value: Date) => { ... })

解决方案是将 spinner / default 模式的 DatePicker 和 time 模式的 TimePicker 统一替换为 CalendarPicker(与 inline / compact 模式一致)。修改 oh_modules 中的 RNDateTimePicker.ets 后,重新执行 node scripts/rebuild-har.js 并 Clean Build。

Q5:HAR 安装失败(ohpm Sync 报错)

检查 oh-package.json5 中 HAR 路径是否正确。路径从 harmony/ 目录算起,到根 node_modules 需要回退三级:

"@react-native-ohos/datetimepicker": "file:../../../node_modules/@react-native-oh-tpl/datetimepicker/harmony/datetimepicker.har"

路径层级写错会导致 ohpm 找不到 HAR 文件。

Q6:真机安装失败(HAP 安装报错)

用 DevEco Studio 打开工程,进入 File > Project Structure > Signing Configs,勾选 Automatically generate signature 后重新运行。

5.2 库本身存在问题:如何提交 Issue

  1. 打开适配仓库 https://atomgit.com/CPF-RN/rntpc_datetimepicker 的 Issues 页面,点击"新建 Issue";
  2. 标题格式:[Bug] 一句话现象,例如 [Bug] DateTimePicker spinner 模式不渲染;
  3. 正文必须包含:复现步骤 / 期望结果 / 实际结果 / 设备与系统版本 / RNOH 版本 / 最小复现代码、日志或截图;
  4. 提交后跟踪仓库维护者回复,修复发布后关注对应 Tag 更新依赖版本。

5.3 能自己解决:如何提交 PR

  1. Fork 适配仓库 https://atomgit.com/CPF-RN/rntpc_datetimepicker 到个人 AtomGit 账号;
  2. git clone 自己的 fork,基于 master 新建分支:git checkout -b fix/xxx;
  3. 修改代码(如 ArkTS 侧组件实现、JS 侧属性映射)并 commit;
  4. push 到自己的 fork,在原仓库发起 Pull Request;
  5. PR 描述写清:问题背景 / 修改点 / 鸿蒙真机验证结果(附运行截图),等待维护者评审合入。

六、其他内容

6.1 总结

@react-native-oh-tpl/datetimepicker 为 React Native 鸿蒙应用补齐了日期时间选择能力。与 file-selector(TurboModule 命令式调用)不同,datetimepicker 是 Fabric UI 组件,以声明式 JSX 标签的形式直接嵌入页面。接入时注意四点:oh-package.json5 中 HAR 路径层级要正确、当前版本 HAR 存在打包缺陷需通过 rebuild-har.js 脚本重建、Fabric 组件必须通过 style 指定尺寸否则不可见、spinner 模式下内置 DatePicker / TimePicker 在当前 SDK 版本存在兼容性问题需替换为 CalendarPicker。建议生产环境锁定依赖版本,遇到问题优先查看适配仓库 Issues。

6.2 与 file-selector 的对比

维度file-selectordatetimepicker
组件类型TurboModule(原生模块)Fabric UI 组件(原生视图)
调用方式FileSelector.Show({…}) 命令式<DateTimePicker … /> 声明式
注册方式需手动创建本地 Package支持 autolinking 自动注册
HAR 状态基于旧版框架,需本地替代有打包缺陷,需 rebuild-har.js 修复
尺寸要求无(非 UI 组件)必须指定 style={{ flex: 1 }}

6.3 参考链接

RNOH 社区入口和三方库资源统一在这里:

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐