在华为鸿蒙(HarmonyOS)操作系统上进行React Native开发,涉及到文件上传功能,你可以通过使用一些现有的库或者自己封装一些原生模块来实现。鸿蒙系统是基于OpenHarmony开发的,所以大多数OpenHarmony的文件上传解决方案同样适用于鸿蒙。以下是一些步骤和技巧,帮助你在React Native项目中实现文件上传功能:

  1. 使用现有的React Native库

RNFS (React Native File System)

react-native-fs 是一个常用的库,可以用来处理文件系统操作,包括读取和写入文件。虽然它主要用于文件操作,但你可以用它来准备文件然后通过其他方式上传。

安装:

npm install react-native-fs

示例代码:

import RNFS from 'react-native-fs';

async function uploadFile() {
  const filePath = RNFS.DocumentDirectoryPath + '/myfile.txt';
  const fileData = 'Hello, world!';
  await RNFS.writeFile(filePath, fileData, 'utf8');

  // 然后可以使用其他库如 `react-native-fetch-blob` 或 `axios` 来上传文件
}

react-native-fetch-blob

react-native-fetch-blob 是一个强大的库,可以用来上传文件。

安装:

npm install --save react-native-fetch-blob

示例代码:

import RNFetchBlob from 'react-native-fetch-blob';

async function uploadFile() {
  const filePath = `${RNFS.DocumentDirectoryPath}/myfile.txt`;
  const fileData = 'Hello, world!';
  await RNFS.writeFile(filePath, fileData, 'utf8');

  const { dirs } = RNFetchBlob.fs;
  const uploadUrl = 'https://yourserver.com/upload'; // 你的上传URL
  RNFetchBlob.fetch('POST', uploadUrl, {
    // 'Content-Type' : 'multipart/form-data', // 可选,根据你的服务器需求设置
    'otherHeader' : 'foo', // 其他自定义头部信息
  }, [
    { name: 'myfile.txt', filename: 'myfile.txt', filepath: filePath }, // 文件信息
  ]).then((resp) => {
    console.log('Upload success', resp);
  }).catch((err) => {
    console.log('Upload error', err);
  });
}
  1. 使用原生模块(如果你需要更深入的集成)

如果你需要更深入的控制或者遇到库无法解决的问题,你可以创建一个原生模块。例如,你可以为OpenHarmony创建一个Java/Kotlin模块,然后在鸿蒙(HarmonyOS)上使用相同的代码。

OpenHarmony 原生模块示例:

Java/Kotlin 文件上传代码:

// 在你的 OpenHarmony 项目中,例如在某个服务或Activity中:
import okhttp3.*; // 需要添加 OkHttp 依赖到你的 build.gradle 文件中
import java.io.*; // 处理文件流等操作需要这个库
import okio.*; // OkHttp 使用的库来处理流等操作

public void uploadFile(String filePath) {
    OkHttpClient client = new OkHttpClient(); // 创建 OkHttp 客户端实例
    RequestBody requestBody = new MultipartBody.Builder() // 创建 MultipartBody 来构建请求体,用于文件上传
        .setType(MultipartBody.FORM) // 设置表单类型,你也可以使用其他类型如 MultipartBody.MIXED 等根据需要选择合适的类型。
        .addFormDataPart("file", "myfile.txt", RequestBody.create(MediaType.parse("text/plain"), new File(filePath))) // 添加文件数据部分到请求体中。这里 "file" 是后端期待的字段名,"myfile.txt" 是文件名,最后的 create 方法是用来创建 RequestBody 的实例。MediaType.parse("text/plain") 是文件的 MIME 类型,根据你的文件类型进行更改。例如图片可以是 "image/jpeg"。new File(filePath) 是文件的路径。
        .build(); // 构建请求体对象。
    Request request = new Request.Builder() // 创建请求对象。这里用的是 POST 方法。你也可以使用其他方法如 GET 等。具体取决于你的后端API设计。
        .url("https://yourserver.com/upload") // 设置请求的 URL。这里替换成你的服务器地址和上传接口。
        .post(requestBody) // 将请求体设置到请求对象中。这里用的是 POST 方法,所以用的是 post 方法。如果是 GET 方法

真实案列演示效果:

import React, { useState } from 'react';
import { View, Text, StyleSheet, ScrollView, Dimensions, TouchableOpacity, Alert, Image } from 'react-native';

// Simple Icon Component using Unicode symbols
interface IconProps {
  name: string;
  size?: number;
  color?: string;
  style?: object;
}

const Icon: React.FC<IconProps> = ({ 
  name, 
  size = 24, 
  color = '#333333',
  style 
}) => {
  const getIconSymbol = () => {
    switch (name) {
      case 'upload': return '📤';
      case 'file': return '📄';
      case 'image': return '🖼️';
      case 'document': return '📑';
      case 'delete': return '🗑️';
      case 'success': return '✅';
      case 'error': return '❌';
      case 'camera': return '📷';
      case 'gallery': return '🖼️';
      case 'pdf': return '📕';
      case 'excel': return '📊';
      case 'word': return '📝';
      default: return '📎';
    }
  };

  return (
    <View style={[{ width: size, height: size, justifyContent: 'center', alignItems: 'center' }, style]}>
      <Text style={{ fontSize: size * 0.8, color, includeFontPadding: false, textAlign: 'center' }}>
        {getIconSymbol()}
      </Text>
    </View>
  );
};

// File Type Icon Component
const getFileTypeIcon = (type: string, size: number = 24) => {
  if (type.includes('image')) return <Icon name="image" size={size} color="#1890ff" />;
  if (type.includes('pdf')) return <Icon name="pdf" size={size} color="#ff4d4f" />;
  if (type.includes('sheet')) return <Icon name="excel" size={size} color="#52c41a" />;
  if (type.includes('document')) return <Icon name="word" size={size} color="#1890ff" />;
  return <Icon name="file" size={size} color="#999999" />;
};

// File Item Component
interface FileItemProps {
  file: {
    id: string;
    name: string;
    size: number;
    type: string;
    uri?: string;
    progress?: number;
    status: 'uploading' | 'success' | 'error';
  };
  onDelete: (id: string) => void;
}

const FileItem: React.FC<FileItemProps> = ({ file, onDelete }) => {
  const formatFileSize = (bytes: number) => {
    if (bytes < 1024) return bytes + ' B';
    if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
    return (bytes / 1048576).toFixed(1) + ' MB';
  };

  const getStatusColor = () => {
    switch (file.status) {
      case 'success': return '#52c41a';
      case 'error': return '#ff4d4f';
      default: return '#1890ff';
    }
  };

  const getStatusText = () => {
    switch (file.status) {
      case 'uploading': return '上传中...';
      case 'success': return '上传成功';
      case 'error': return '上传失败';
      default: return '';
    }
  };

  return (
    <View style={styles.fileItemContainer}>
      <View style={styles.fileIcon}>
        {file.uri && file.type.includes('image') ? (
          <Image source={{ uri: file.uri }} style={styles.fileThumbnail} />
        ) : (
          getFileTypeIcon(file.type, 32)
        )}
      </View>
      
      <View style={styles.fileInfo}>
        <Text style={styles.fileName} numberOfLines={1}>{file.name}</Text>
        <Text style={styles.fileMeta}>
          {formatFileSize(file.size)}{file.type.split('/')[1]?.toUpperCase() || 'FILE'}
        </Text>
        <View style={styles.fileStatusContainer}>
          <Text style={[styles.fileStatus, { color: getStatusColor() }]}>
            {getStatusText()}
          </Text>
          {file.status === 'uploading' && (
            <View style={styles.progressBar}>
              <View 
                style={[
                  styles.progressFill, 
                  { width: `${file.progress || 0}%`, backgroundColor: getStatusColor() }
                ]} 
              />
            </View>
          )}
        </View>
      </View>
      
      <TouchableOpacity 
        style={styles.deleteButton}
        onPress={() => onDelete(file.id)}
      >
        <Icon name="delete" size={20} color="#ff4d4f" />
      </TouchableOpacity>
    </View>
  );
};

// Uploader Component
interface UploaderProps {
  onUpload: (files: any[]) => void;
  maxFiles?: number;
  accept?: string[];
  multiple?: boolean;
}

const Uploader: React.FC<UploaderProps> = ({ 
  onUpload,
  maxFiles = 5,
  accept = [],
  multiple = true
}) => {
  const [files, setFiles] = useState<any[]>([]);
  const [isUploading, setIsUploading] = useState(false);

  const selectFile = () => {
    if (files.length >= maxFiles) {
      Alert.alert('提示', `最多只能上传${maxFiles}个文件`);
      return;
    }
    
    Alert.alert(
      '选择文件',
      '请选择文件来源',
      [
        {
          text: '拍照',
          onPress: () => simulateCameraCapture()
        },
        {
          text: '从相册选择',
          onPress: () => simulateGallerySelection()
        },
        {
          text: '从文件管理器选择',
          onPress: () => simulateFileManagerSelection()
        },
        {
          text: '取消',
          style: 'cancel'
        }
      ]
    );
  };

  // Simulate file selection (in real app, you would use libraries like react-native-image-picker)
  const simulateCameraCapture = () => {
    const newFile = {
      id: Date.now().toString(),
      name: `photo_${Date.now()}.jpg`,
      size: Math.floor(Math.random() * 5000000) + 1000000,
      type: 'image/jpeg',
      uri: 'https://picsum.photos/200/200?random=' + Math.floor(Math.random() * 100),
      status: 'uploading' as const,
      progress: 0
    };
    
    setFiles(prev => [...prev, newFile]);
    simulateUpload(newFile.id);
  };

  const simulateGallerySelection = () => {
    const types = [
      { name: `image_${Date.now()}.png`, type: 'image/png' },
      { name: `document_${Date.now()}.pdf`, type: 'application/pdf' },
      { name: `spreadsheet_${Date.now()}.xlsx`, type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' },
      { name: `report_${Date.now()}.docx`, type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }
    ];
    
    const randomType = types[Math.floor(Math.random() * types.length)];
    
    const newFile = {
      id: Date.now().toString(),
      name: randomType.name,
      size: Math.floor(Math.random() * 10000000) + 500000,
      type: randomType.type,
      uri: randomType.type.includes('image') 
        ? 'https://picsum.photos/200/200?random=' + Math.floor(Math.random() * 100)
        : undefined,
      status: 'uploading' as const,
      progress: 0
    };
    
    setFiles(prev => [...prev, newFile]);
    simulateUpload(newFile.id);
  };

  const simulateFileManagerSelection = () => {
    simulateGallerySelection();
  };

  const simulateUpload = (fileId: string) => {
    const interval = setInterval(() => {
      setFiles(prev => prev.map(file => {
        if (file.id === fileId && file.status === 'uploading') {
          const newProgress = Math.min((file.progress || 0) + 10, 100);
          
          if (newProgress === 100) {
            clearInterval(interval);
            return { ...file, progress: 100, status: 'success' };
          }
          
          return { ...file, progress: newProgress };
        }
        return file;
      }));
    }, 300);
  };

  const deleteFile = (id: string) => {
    setFiles(prev => prev.filter(file => file.id !== id));
  };

  const retryUpload = (id: string) => {
    setFiles(prev => prev.map(file => 
      file.id === id ? { ...file, status: 'uploading', progress: 0 } : file
    ));
    simulateUpload(id);
  };

  return (
    <View style={styles.uploaderContainer}>
      <TouchableOpacity 
        style={styles.uploadArea}
        onPress={selectFile}
        disabled={isUploading}
      >
        <Icon name="upload" size={48} color="#1890ff" />
        <Text style={styles.uploadText}>点击上传文件</Text>
        <Text style={styles.uploadHint}>
          支持 JPGPNGPDFDOC 等格式
        </Text>
        <Text style={styles.uploadLimit}>
          最多可上传 {maxFiles} 个文件
        </Text>
      </TouchableOpacity>
      
      {files.length > 0 && (
        <View style={styles.filesContainer}>
          {files.map(file => (
            <FileItem 
              key={file.id} 
              file={file} 
              onDelete={file.status === 'uploading' ? () => {} : deleteFile}
            />
          ))}
        </View>
      )}
      
      {files.some(f => f.status === 'error') && (
        <TouchableOpacity 
          style={styles.retryButton}
          onPress={() => files.forEach(f => f.status === 'error' && retryUpload(f.id))}
        >
          <Text style={styles.retryButtonText}>重新上传失败文件</Text>
        </TouchableOpacity>
      )}
    </View>
  );
};

// Main App Component
const UploaderComponentApp = () => {
  const handleUpload = (files: any[]) => {
    console.log('Uploaded files:', files);
  };

  return (
    <ScrollView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerTitle}>文件上传组件</Text>
        <Text style={styles.headerSubtitle}>美观实用的文件上传控件</Text>
      </View>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>基础用法</Text>
        <View style={styles.uploaderSection}>
          <Uploader 
            onUpload={handleUpload}
            maxFiles={5}
            multiple
          />
        </View>
      </View>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>上传说明</Text>
        <View style={styles.infoSection}>
          <View style={styles.infoItem}>
            <Icon name="file" size={20} color="#1890ff" style={styles.infoIcon} />
            <Text style={styles.infoText}>支持常见文件格式:图片、文档、表格等</Text>
          </View>
          
          <View style={styles.infoItem}>
            <Icon name="image" size={20} color="#52c41a" style={styles.infoIcon} />
            <Text style={styles.infoText}>图片文件支持预览显示</Text>
          </View>
          
          <View style={styles.infoItem}>
            <Icon name="success" size={20} color="#fa8c16" style={styles.infoIcon} />
            <Text style={styles.infoText}>上传进度实时显示</Text>
          </View>
        </View>
      </View>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>功能演示</Text>
        <View style={styles.demosContainer}>
          <View style={styles.demoItem}>
            <Icon name="upload" size={24} color="#1890ff" style={styles.demoIcon} />
            <View>
              <Text style={styles.demoTitle}>多文件上传</Text>
              <Text style={styles.demoDesc}>支持同时上传多个文件</Text>
            </View>
          </View>
          
          <View style={styles.demoItem}>
            <Icon name="image" size={24} color="#52c41a" style={styles.demoIcon} />
            <View>
              <Text style={styles.demoTitle}>图片预览</Text>
              <Text style={styles.demoDesc}>图片文件支持缩略图预览</Text>
            </View>
          </View>
          
          <View style={styles.demoItem}>
            <Icon name="success" size={24} color="#722ed1" style={styles.demoIcon} />
            <View>
              <Text style={styles.demoTitle}>状态反馈</Text>
              <Text style={styles.demoDesc}>上传进度和结果实时反馈</Text>
            </View>
          </View>
        </View>
      </View>
      
      <View style={styles.usageSection}>
        <Text style={styles.sectionTitle}>使用方法</Text>
        <View style={styles.codeBlock}>
          <Text style={styles.codeText}>{'<Uploader'}</Text>
          <Text style={styles.codeText}>  onUpload={'{handleUpload}'}</Text>
          <Text style={styles.codeText}>  maxFiles={'{5}'}</Text>
          <Text style={styles.codeText}>  multiple{'\n'}/></Text>
        </View>
        <Text style={styles.description}>
          Uploader组件提供了完整的文件上传功能,包括文件选择、上传进度、状态反馈等。
          通过onUpload处理上传完成事件,支持自定义文件数量限制和文件类型。
        </Text>
      </View>
      
      <View style={styles.featuresSection}>
        <Text style={styles.sectionTitle}>功能特性</Text>
        <View style={styles.featuresList}>
          <View style={styles.featureItem}>
            <Icon name="upload" size={20} color="#1890ff" style={styles.featureIcon} />
            <Text style={styles.featureText}>多文件上传</Text>
          </View>
          <View style={styles.featureItem}>
            <Icon name="image" size={20} color="#52c41a" style={styles.featureIcon} />
            <Text style={styles.featureText}>图片预览</Text>
          </View>
          <View style={styles.featureItem}>
            <Icon name="success" size={20} color="#722ed1" style={styles.featureIcon} />
            <Text style={styles.featureText}>进度反馈</Text>
          </View>
          <View style={styles.featureItem}>
            <Icon name="file" size={20} color="#fa8c16" style={styles.featureIcon} />
            <Text style={styles.featureText}>格式支持</Text>
          </View>
        </View>
      </View>
      
      <View style={styles.footer}>
        <Text style={styles.footerText}>© 2023 文件上传组件 | 现代化UI组件库</Text>
      </View>
    </ScrollView>
  );
};

const { width } = Dimensions.get('window');

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#ffffff',
  },
  header: {
    backgroundColor: '#f0f5ff',
    paddingVertical: 30,
    paddingHorizontal: 20,
    marginBottom: 10,
    borderBottomWidth: 1,
    borderBottomColor: '#e1ebff',
  },
  headerTitle: {
    fontSize: 28,
    fontWeight: '700',
    color: '#1d39c4',
    textAlign: 'center',
    marginBottom: 5,
  },
  headerSubtitle: {
    fontSize: 16,
    color: '#597ef7',
    textAlign: 'center',
  },
  section: {
    marginBottom: 25,
  },
  sectionTitle: {
    fontSize: 20,
    fontWeight: '700',
    color: '#1d39c4',
    paddingHorizontal: 20,
    paddingBottom: 15,
  },
  uploaderSection: {
    backgroundColor: '#ffffff',
    marginHorizontal: 15,
    borderRadius: 12,
    padding: 20,
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 4,
  },
  infoSection: {
    backgroundColor: '#ffffff',
    marginHorizontal: 15,
    borderRadius: 12,
    padding: 20,
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 4,
  },
  infoItem: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 15,
  },
  infoItemLast: {
    marginBottom: 0,
  },
  infoIcon: {
    marginRight: 15,
  },
  infoText: {
    fontSize: 15,
    color: '#595959',
    flex: 1,
  },
  demosContainer: {
    backgroundColor: '#ffffff',
    marginHorizontal: 15,
    borderRadius: 15,
    padding: 20,
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 4,
  },
  demoItem: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 20,
  },
  demoItemLast: {
    marginBottom: 0,
  },
  demoIcon: {
    marginRight: 15,
  },
  demoTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#262626',
    marginBottom: 3,
  },
  demoDesc: {
    fontSize: 14,
    color: '#8c8c8c',
  },
  usageSection: {
    backgroundColor: '#ffffff',
    marginHorizontal: 15,
    borderRadius: 15,
    padding: 20,
    marginBottom: 20,
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 4,
  },
  codeBlock: {
    backgroundColor: '#1d2b57',
    borderRadius: 8,
    padding: 15,
    marginBottom: 15,
  },
  codeText: {
    fontFamily: 'monospace',
    color: '#c9d1d9',
    fontSize: 14,
    lineHeight: 22,
  },
  description: {
    fontSize: 15,
    color: '#595959',
    lineHeight: 22,
  },
  featuresSection: {
    backgroundColor: '#ffffff',
    marginHorizontal: 15,
    borderRadius: 15,
    padding: 20,
    marginBottom: 20,
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 4,
  },
  featuresList: {
    paddingLeft: 10,
  },
  featureItem: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 15,
  },
  featureIcon: {
    marginRight: 15,
  },
  featureText: {
    fontSize: 16,
    color: '#262626',
  },
  footer: {
    paddingVertical: 20,
    alignItems: 'center',
  },
  footerText: {
    color: '#bfbfbf',
    fontSize: 14,
  },
  // Uploader Styles
  uploaderContainer: {
    marginBottom: 15,
  },
  uploadArea: {
    borderWidth: 2,
    borderStyle: 'dashed',
    borderColor: '#91caff',
    borderRadius: 12,
    padding: 30,
    alignItems: 'center',
    backgroundColor: '#f0faff',
  },
  uploadText: {
    fontSize: 18,
    fontWeight: '600',
    color: '#1d39c4',
    marginTop: 15,
    marginBottom: 10,
  },
  uploadHint: {
    fontSize: 14,
    color: '#597ef7',
    marginBottom: 5,
  },
  uploadLimit: {
    fontSize: 13,
    color: '#8c8c8c',
  },
  filesContainer: {
    marginTop: 20,
  },
  fileItemContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 15,
    backgroundColor: '#fafafa',
    borderRadius: 8,
    marginBottom: 10,
    borderWidth: 1,
    borderColor: '#f0f0f0',
  },
  fileIcon: {
    marginRight: 15,
  },
  fileThumbnail: {
    width: 40,
    height: 40,
    borderRadius: 4,
  },
  fileInfo: {
    flex: 1,
  },
  fileName: {
    fontSize: 16,
    fontWeight: '500',
    color: '#262626',
    marginBottom: 3,
  },
  fileMeta: {
    fontSize: 13,
    color: '#8c8c8c',
    marginBottom: 5,
  },
  fileStatusContainer: {
    flexDirection: 'column',
  },
  fileStatus: {
    fontSize: 13,
    fontWeight: '500',
  },
  progressBar: {
    height: 4,
    backgroundColor: '#f0f0f0',
    borderRadius: 2,
    marginTop: 5,
    overflow: 'hidden',
  },
  progressFill: {
    height: '100%',
    borderRadius: 2,
  },
  deleteButton: {
    padding: 5,
  },
  retryButton: {
    backgroundColor: '#1890ff',
    borderRadius: 6,
    paddingVertical: 12,
    alignItems: 'center',
    marginTop: 15,
  },
  retryButtonText: {
    color: '#ffffff',
    fontSize: 16,
    fontWeight: '500',
  },
});

export default UploaderComponentApp;

从鸿蒙ArkUI开发角度分析,这段React Native文件上传组件的逻辑体现了鸿蒙分布式文件管理系统的设计理念。Icon组件通过Unicode符号映射实现图标显示,这与鸿蒙的Symbol组件设计思路一致,但鸿蒙提供了更完善的图标资源管理机制,可以通过$r(‘app.media.icon_name’)直接引用资源文件。

FileItem组件在鸿蒙中对应着List组件的项渲染,通过@Builder装饰器构建每个文件项的UI布局。文件类型识别逻辑通过getFileTypeIcon函数实现,基于MIME类型匹配对应图标,这与鸿蒙的文件类型关联机制相似。鸿蒙通过FileExtension枚举定义文件类型,支持更精确的类型匹配。

状态管理方面,文件的上传状态(uploading/success/error)通过@State装饰器管理,当状态变更时自动触发UI更新。进度条显示逻辑对应鸿蒙的Progress组件,通过value属性绑定上传进度数值。图片缩略图展示使用鸿蒙的Image组件,支持本地和网络图片的异步加载和缓存。

在这里插入图片描述

Uploader组件的文件选择逻辑在鸿蒙中通过@ohos.file.picker模块实现,支持相机拍照、相册选择和文件管理器三种来源。这与代码中的Alert选择器功能对应,但鸿蒙提供了更完整的文件选择API。最大文件数限制通过maxFiles参数控制,这与鸿蒙Picker的maxSelectNumber配置一致。

文件删除操作在鸿蒙中通过@ohos.file.fs模块的删除接口实现,需要申请相应的文件访问权限。鸿蒙的安全机制要求在使用文件操作前必须通过requestPermissionsFromUser申请权限。

组件通信采用回调函数模式,onUpload和onDelete对应鸿蒙的自定义事件机制,通过CustomDialogController管理弹窗交互。文件大小格式化函数在鸿蒙中可以通过@ohos.i18n的国际化能力实现更友好的显示。


打包

接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

在这里插入图片描述

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

在这里插入图片描述

最后运行效果图如下显示:

请添加图片描述

Logo

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

更多推荐