碰一碰分享是HarmonyOS NEXT上很有代表性的近场交互能力:两台手机顶部轻碰,商品信息、文件或链接就能直接传过去。相比扫码和链接跳转,“碰”这个动作的成本极低,在电商、社交等强分享场景里尤其好用。这篇文章记录我们在HarmonyOS NEXT上把碰一碰分享接入React Native(RN)的开发经验,包括TurboModule桥接方案、核心接口使用、踩过的坑和分析思路。本文基于React Native 0.84 + RNOH 0.84.1编写,设备为HarmonyOS 6.0,不同版本间可能有差异,以实际测试为准。
一、整体方案:Share Kit + 自定义TurboModule
碰一碰分享底层依赖的是HarmonyOS Share Kit的近场发现能力。在原生鸿蒙应用里可以直接调用Share Kit接口,但在RN开发模式下,需要通过自定义TurboModule把原生能力暴露给JS侧。我们实现了一个名为TapShareModule的桥接模块,对上层提供三个核心方法:
- import TapShareModule from '../native/NativeTapShareModule';
- // 检查是否支持碰一碰
- const available = await TapShareModule.isTapShareAvailable();
- // 注册分享数据
- const result = await TapShareModule.shareSku(payload);
- // 停止分享
- await TapShareModule.stopShare();
复制代码
shareSku接收的payload承载了分享的业务数据,其中targetRoute是接收方点击Deep Link拉起应用后要跳转的路由,sourceScene用于标识分享来源场景,方便后续做数据统计。
- interface TapShareSkuPayload {
- skuId: string;
- spuId?: string;
- title: string;
- imageUrl?: string;
- price?: string;
- currency?: string;
- stockStatus?: 'normal' | 'low' | 'out_of_stock';
- targetRoute: string; // 接收方打开应用时跳转的路由
- sourceScene: string; // 来源场景
- }
- interface TapShareResult {
- code: number;
- message: string;
- shareId?: string;
- status: 'waiting' | 'success' | 'cancelled' | 'failed';
- targetRoute?: string;
- }
复制代码
二、能力检测:必须真机,不能跳过
Share Kit依赖NFC等近场通信硬件,模拟器不支持,必须真机测试。因此开发中首先要做好能力检测,否则在不支持的设备上直接调用shareSku会抛异常,影响用户体验。
- const checkSupport = async () => {
- if (!TapShareModule) {
- console.log('TapShareModule 未注册');
- return false;
- }
- try {
- const available = await TapShareModule.isTapShareAvailable();
- console.log('碰一碰分享能力:', available ? '可用' : '不可用');
- return available;
- } catch (error) {
- console.error('检测失败:', error);
- return false;
- }
- };
复制代码
如果设备或系统版本不支持,isTapShareAvailable()会返回false。分享入口需要据此做降级处理,比如改用链接分享或二维码。
三、两个典型错误写法
错误一:不检查能力可用性直接调用shareSku
- // 错误写法 直接调用 shareSku
- const handleShare = async () => {
- const result = await TapShareModule.shareSku(payload);
- // 如果在不支持 Share Kit 的设备上,会抛异常
- };
复制代码
正确做法是先判断模块是否存在、能力是否可用,再决定走碰一碰还是降级方案。
- // 正确写法 先检查能力可用性
- const handleShare = async () => {
- if (!TapShareModule) {
- Alert.alert('提示', 'TapShare 模块未注册');
- return;
- }
- const available = await TapShareModule.isTapShareAvailable();
- if (!available) {
- Alert.alert('提示', '当前设备不支持碰一碰分享');
- return;
- }
- const result = await TapShareModule.shareSku(payload);
- };
复制代码
错误二:分享成功后不清理状态
shareSku的返回值是异步的,上次分享的结果如果不清理,下次进入页面时可能会影响UI判断。
- // 错误写法 分享后不处理
- const handleShare = () => {
- TapShareModule.shareSku(payload);
- // 没有清理状态,下次分享时上次的结果还在
- };
- // 正确写法 分享前后管理状态
- const [result, setResult] = useState<TapShareResult | null>(null);
- const handleShare = async () => {
- setResult(null); // 清空上次结果
- const res = await TapShareModule.shareSku(payload);
- setResult(res);
- };
复制代码
四、完整实战:SKU分享组件
以电商商品详情页为例,分享按钮的完整实现包含状态管理、payload构建和结果展示:
- const SkuShareButton = ({ sku }) => {
- const [sharing, setSharing] = useState(false);
- const [result, setResult] = useState(null);
- const buildPayload = (sku) => ({
- skuId: sku.id,
- spuId: sku.spuId,
- title: sku.title,
- imageUrl: sku.image,
- price: sku.price,
- currency: 'USD',
- stockStatus: sku.stock > 10 ? 'normal' : 'low',
- targetRoute: `/sku/detail?skuId=${sku.id}`,
- sourceScene: 'sku_detail',
- });
- const handleShare = async () => {
- setSharing(true);
- try {
- if (!TapShareModule) {
- throw new Error('模块未注册');
- }
- const payload = buildPayload(sku);
- const res = await TapShareModule.shareSku(payload);
- setResult(res);
- } catch (error) {
- Alert.alert('分享失败', String(error));
- } finally {
- setSharing(false);
- }
- };
- return (
- <View>
- <Pressable onPress={handleShare} disabled={sharing}>
- <Text>{sharing ? '准备中...' : '碰一碰分享'}</Text>
- </Pressable>
- {result?.status === 'waiting' && (
- <View>
- <Text>已注册,请碰一碰手机</Text>
- <Pressable onPress={() => TapShareModule.stopShare()}>
- <Text>取消</Text>
- </Pressable>
- </View>
- )}
- </View>
- );
- };
复制代码
调用shareSku成功后,系统会注册一份待分享内容,两台支持碰一碰的设备亮屏解锁后顶部轻碰,数据即开始传输。用户点击取消时调用stopShare解除注册;如果已经分享完成,stopShare不影响已完成的传输。
接收端通过Deep Link拉起应用,在App.tsx用Linking监听URL并解析参数:
- const handleDeepLink = (url: string) => {
- const parsed = parseTapShareUrl(url);
- if (parsed) {
- // 跳转到 SKU 详情页
- navigation.navigate('SkuDetail', { skuId: parsed.skuId });
- }
- };
- const parseTapShareUrl = (url: string): TapShareSkuPayload | null => {
- try {
- // 示例 URL: skuassistant://sku/detail?skuId=SKU-HM-001&title=...
- const match = url.match(/skuassistant:\/\/sku\/detail\?(.+)/);
- if (!match) return null;
- const params = new URLSearchParams(match[1]);
- return {
- skuId: params.get('skuId') || '',
- title: params.get('title') || '',
- targetRoute: url,
- sourceScene: 'sku_detail',
- };
- } catch {
- return null;
- }
- };
复制代码
五、原生桥接实现三层结构
TapShareModule的桥接实现分三层。第一层是JS侧接口定义:
- import type { TurboModule } from 'react-native';
- import { TurboModuleRegistry } from 'react-native';
- export interface TapShareSkuPayload {
- skuId: string;
- spuId?: string;
- title: string;
- imageUrl?: string;
- price?: string;
- currency?: string;
- stockStatus?: string;
- targetRoute: string;
- sourceScene: string;
- }
- export interface TapShareResult {
- code: number;
- message: string;
- shareId?: string;
- status: string;
- targetRoute?: string;
- }
- export interface Spec extends TurboModule {
- shareSku(payload: TapShareSkuPayload): Promise<TapShareResult>;
- stopShare(): Promise<boolean>;
- isTapShareAvailable(): Promise<boolean>;
- }
- export default TurboModuleRegistry.getEnforcing<Spec>('TapShareModule');
复制代码
第二层是C++方法映射,把JS调用映射到ArkTS实现:
- TapShareModule::TapShareModule(const ArkTSTurboModule::Context ctx, const std::string name)
- : ArkTSTurboModule(ctx, name) {
- methodMap_ = {
- ARK_ASYNC_METHOD_METADATA(shareSku, 1),
- ARK_ASYNC_METHOD_METADATA(stopShare, 0),
- ARK_ASYNC_METHOD_METADATA(isTapShareAvailable, 0),
- };
- }
复制代码
第三层是ArkTS原生实现。能力检测用HarmonyOS的canIUse API判断当前系统是否支持Share Kit:
- import { harmonyShare } from '@kit.ShareKit';
- async isTapShareAvailable(): Promise<boolean> {
- return canIUse('SystemCapability.Collaboration.HarmonyShare');
- }
复制代码
注册碰一碰分享则通过harmonyShare的事件监听实现:
- // 注册碰一碰事件监听
- harmonyShare.on('knockShare', (sharableTarget) => {
- // 碰一碰触发后的回调
- this.handleKnockShare(sharableTarget);
- });
- // 取消注册
- harmonyShare.off('knockShare');
复制代码
原生侧根据传递的payload参数构造JSON数据,通过Share Kit的sharableTarget分发到接收端,接收端再通过Deep Link拉起应用并传递参数。
六、错误码速查与状态处理
实际开发中,建议为TapShare维护一份错误码表,方便定位问题和展示给用户:
- const TapShareErrorCodes = {
- 1001: { level: 'info', message: '已注册,等待碰一碰' },
- 1002: { level: 'info', message: '已更新待分享内容' },
- 2001: { level: 'info', message: '已解除注册' },
- 3001: { level: 'warn', message: '系统不支持 Share Kit' },
- 3002: { level: 'error', message: '模块未注册' },
- 5001: { level: 'error', message: '调用失败' },
- 5002: { level: 'error', message: '解除注册失败' },
- };
- const handleTapShareResult = (result: TapShareResult) => {
- const codeInfo = TapShareErrorCodes[result.code];
- if (codeInfo.level === 'error') {
- console.error(`[TapShare] ${codeInfo.message}: ${result.message}`);
- } else if (codeInfo.level === 'warn') {
- console.warn(`[TapShare] ${codeInfo.message}`);
- } else {
- console.log(`[TapShare] ${codeInfo.message}`);
- }
- };
复制代码
接收端拿到Deep Link后,页面需要根据解析出的skuId等标识从API拉取完整的商品数据渲染。不要在payload里塞全量数据。
七、鸿蒙上的坑:五个关键注意点
坑1:必须真机测试。Share Kit依赖NFC和近场通信硬件,模拟器上不可用。开发调试时一定用真机,并且要准备至少两台HarmonyOS NEXT设备。
坑2:不能实时获知传输结果。发送端无法直接获取接收端是否成功接收。真正的结果由HarmonyOS系统通过通知告知用户。所以分享后的UI提示要明确说明“等待碰一碰触发”,而不是“传输成功”。
坑3:设备要求严格。两台设备都必须是HarmonyOS NEXT,都亮屏解锁,顶部区域相互接触。任何条件不满足都可能触发失败。
坑4:分享数据大小限制。当前版本中payload的总数据量不宜过大,建议只传必要的业务标识(skuId、targetRoute),详细数据由接收端通过API自行拉取。这样payload小了,传输更快,数据也更安全。
坑5:业务标识vs完整数据。不要把商品描述、价格、图片全塞进payload,接收端拿到Deep Link后按需请求接口即可。
八、适配步骤与降级建议
发送端适配四步走:先检测能力,不可用则降级到URL分享;构建最小payload;注册分享;UI提示用户碰一碰。
- // 1. 检测能力
- const available = await TapShareModule.isTapShareAvailable();
- if (!available) {
- // 降级到 URL 分享
- return shareByUrl(payload);
- }
- // 2. 构建 payload
- const payload = buildMinimalPayload(sku);
- // 3. 注册分享
- const result = await TapShareModule.shareSku(payload);
- // 4. UI 提示
- showKnockGuide(result);
复制代码
接收端则配置Deep Link scheme,在App.tsx中监听Linking,解析URL参数,跳转对应页面并拉取完整数据。
还有一个容易被忽略的点:组件卸载时要清理状态。用useEffect的清理函数调用stopShare,避免页面已销毁但分享注册还挂着的情况。
九、结语
碰一碰分享是HarmonyOS的独特能力,其最大价值在于“碰”这个交互成本极低——不需要扫码、不需要点链接、不需要输验证码。在电商场景里,这种交互对于商品分享、邀请拼单等业务有天然的适配空间。但在不支持碰一碰的设备上一定要有降级方案,不能把碰一碰作为唯一分享方式。建议分享场景接上埋点追踪,记录从哪个页面发起的分享、分享状态如何,方便后续优化。
文中代码示例基于HarmonyOS 6.0和RNOH 0.84.1实测通过,可直接复用。版本升级后需重新回归验证。 |