使用 React Native 随时随地使用表格
React Native 是一个移动应用框架。它构建使用 JavaScript 来描述布局和事件的 iOS 和 Android 应用。
¥React Native is a mobile app framework. It builds iOS and Android apps that use JavaScript for describing layouts and events.
SheetJS 是一个用于从电子表格读取和写入数据的 JavaScript 库。
¥SheetJS is a JavaScript library for reading and writing data from spreadsheets.
该演示使用 React Native 和 SheetJS 来处理和生成电子表格。我们将探讨如何通过几种方式在 React Native 应用中加载 SheetJS:
¥This demo uses React Native and SheetJS to process and generate spreadsheets. We'll explore how to load SheetJS in a React Native app in a few ways:
-
"获取远程数据" 使用内置的
fetch
下载并解析远程工作簿文件。¥"Fetching Remote Data" uses the built-in
fetch
to download and parse remote workbook files. -
"本地文件" 使用原生库在设备上读取和写入文件。
¥"Local Files" uses native libraries to read and write files on the device.
"本地文件" 示例创建了一个如下屏幕截图所示的应用:
¥The "Local Files" example creates an app that looks like the screenshots below:
iOS | Android |
---|---|
在测试此演示之前,请遵循官方 React Native CLI 指南![^1]
¥Before testing this demo, follow the official React Native CLI Guide![^1]
请按照 iOS(需要 macOS)和 Android 的说明进行操作。它们将涵盖安装和系统配置。你应该能够在 Android 和 iOS(如果适用)模拟器中构建并运行示例应用。
¥Follow the instructions for iOS (requires macOS) and for Android. They will cover installation and system configuration. You should be able to build and run a sample app in the Android and the iOS (if applicable) simulators.
集成详情
¥Integration Details
SheetJS NodeJS 模块 可以从应用中的任何组件或脚本导入。
¥The SheetJS NodeJS Module can be imported from any component or script in the app.
内部状态
¥Internal State
为简单起见,该演示使用 "数组的数组"[^2] 作为内部状态。
¥For simplicity, this demo uses an "Array of Arrays"[^2] as the internal state.
Spreadsheet | Array of Arrays |
---|---|
|
结构中的每个数组对应一行。
¥Each array within the structure corresponds to one row.
该演示还以单个数字数组的形式跟踪列宽。显示组件使用宽度。
¥This demo also keeps track of the column widths as a single array of numbers. The widths are used by the display component.
完整状态
¥Complete State
完整的状态使用以下代码片段进行初始化:
¥The complete state is initialized with the following snippet:
const [data, setData] = useState([
"SheetJS".split(""),
[5,4,3,3,7,9,5],
[8,6,7,5,3,0,9]
]);
const [widths, setWidths] = useState(Array.from({length:7}, () => 20));
更新状态
¥Updating State
从 SheetJS 工作表对象开始,带有 header
选项的 sheet_to_json
[^3] 可以生成数组的数组:
¥Starting from a SheetJS worksheet object, sheet_to_json
[^3] with the header
option can generate an array of arrays:
/* assuming `wb` is a SheetJS workbook */
function update_state(wb) {
/* convert first worksheet to AOA */
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
const data = utils.sheet_to_json(ws, {header:1});
/* update state */
setData(data);
/* update column widths */
setWidths(make_width(data));
}
计算列宽
¥Calculating Column Widths
可以通过遍历每列并计算最大数据宽度来计算列宽度。使用数组的数组:
¥Column widths can be calculated by walking each column and calculating the max data width. Using the array of arrays:
/* this function takes an array of arrays and generates widths */
function make_width(aoa) {
/* walk each row */
aoa.forEach((r) => {
/* walk each column */
r.forEach((c, C) => {
/* update column width based on the length of the cell contents */
res[C] = Math.max(res[C]||60, String(c).length * 10);
});
});
/* use a default value for columns with no data */
for(let C = 0; C < res.length; ++C) if(!res[C]) res[C] = 60;
return res;
}
导出状态
¥Exporting State
aoa_to_sheet
[^4] 从数组的数组构建一个 SheetJS 工作表对象:
¥aoa_to_sheet
[^4] builds a SheetJS worksheet object from the array of arrays:
/* generate a SheetJS workbook from the state */
function export_state() {
/* convert AOA back to worksheet */
const ws = utils.aoa_to_sheet(data);
/* build new workbook */
const wb = utils.book_new();
utils.book_append_sheet(wb, ws, "SheetJS");
return wb;
}
显示数据
¥Displaying Data
该演示使用 react-native-table-component
显示第一个工作表。
¥The demos uses react-native-table-component
to display the first worksheet.
演示使用类似于以下示例的组件:
¥The demos use components similar to the example below:
import { ScrollView } from 'react-native';
import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
(
{/* Horizontal scroll */}
<ScrollView horizontal={true} >
{/* Table container */}
<Table>
{/* Frozen Header Row */}
<TableWrapper>
{/* First row */}
<Row data={data[0]} widthArr={widths}/>
</TableWrapper>
{/* Scrollable Data Rows */}
<ScrollView>
<TableWrapper>
{/* Remaining Rows */}
<Rows data={data.slice(1)} widthArr={widths}/>
</TableWrapper>
</ScrollView>
</Table>
</ScrollView>
)
Rows
组件中的 data.slice(1)
返回从第二行开始的数据。这巧妙地跳过了第一个标题行。
¥data.slice(1)
in the Rows
component returns data starting from the second
row. This neatly skips the first header row.
获取远程数据
¥Fetching Remote Data
从 0.72.0
[^5] 开始的 React Native 版本支持 fetch
中的二进制数据。
¥React Native versions starting from 0.72.0
[^5] support binary data in fetch
.
此代码片段下载并解析 https://xlsx.nodejs.cn/pres.xlsx:
¥This snippet downloads and parses https://xlsx.nodejs.cn/pres.xlsx:
/* fetch data into an ArrayBuffer */
const ab = await (await fetch("https://xlsx.nodejs.cn/pres.xlsx")).arrayBuffer();
/* parse data */
const wb = XLSX.read(ab);
获取演示
¥Fetch Demo
本 demo 在以下环境下进行了测试:
¥This demo was tested in the following environments:
真实设备
¥Real Devices
OS | 设备 | RN | 日期 |
---|---|---|---|
iOS 15.1 | iPhone 12 Pro 最大 | 0.73.6 | 2024-03-13 |
安卓 29 | 英伟达盾 | 0.73.6 | 2024-03-13 |
模拟器
¥Simulators
OS | 设备 | RN | 开发平台 | 日期 |
---|---|---|---|---|
安卓 34 | 像素 3a | 0.73.6 | darwin-x64 | 2024-03-13 |
iOS 17.4 | iPhone 15 Pro 最大 | 0.73.6 | darwin-x64 | 2024-03-13 |
安卓 34 | 像素 3a | 0.74.2 | darwin-arm | 2024-06-20 |
iOS 17.5 | iPhone SE(第 3 代) | 0.74.2 | darwin-arm | 2024-06-20 |
安卓 34 | 像素 3a | 0.73.5 | win10-x64 | 2024-03-05 |
安卓 34 | 像素 3a | 0.73.7 | linux-x64 | 2024-04-29 |
-
安装 React Native 依赖
¥Install React Native dependencies
Installation Notes (click to show)
On the Steam Deck, JDK17 was installed using pacman
:
sudo pacman -Syu jdk17-openjdk
The Android Studio tarball was downloaded and extracted. After extracting:
cd ./android-studio/bin
./studio.sh
In Android Studio, select "SDK Manager" and switch to the "SDK Tools" tab. Check "Show Package Details" and install "Android SDK Command-line Tools (latest)".
When this demo was last tested, the following environment variables were used:
export ANDROID_HOME=~/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
-
创建项目:
¥Create project:
npx -y react-native@0.74.2 init SheetJSRNFetch --version="0.74.2"
-
安装共享依赖:
¥Install shared dependencies:
cd SheetJSRNFetch
curl -LO https://xlsx.nodejs.cn/logo.png
npm i -S https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz
npm i -S react-native-table-component@1.2.2 @types/react-native-table-component
curl -LO https://xlsx.nodejs.cn/reactnative/App.tsx
安卓测试
¥Android Testing
-
安装或切换到 Java 17[^6]
¥Install or switch to Java 17[^6]
-
启动 Android 模拟器:
¥Start the Android emulator:
npx react-native run-android
在 Linux 上,该命令可能会默默地停止。强烈建议启动交互式 CLI 工具:
¥On Linux, the command may silently stall. It is strongly recommended to launch the interactive CLI tool:
npx react-native start
一旦开发服务器准备就绪,终端将显示一些选项。按 a
在 Android 上运行。
¥Once the dev server is ready, the terminal will display a few options. Press a
to run on Android.
如果初始启动因引用模拟器错误而失败,请从 Android Studio 手动启动模拟器,然后重试。
¥If the initial launch fails with an error referencing the emulator, manually start the emulator from Android Studio and try again.
Gradle 错误通常源于 Java 版本不匹配:
¥Gradle errors typically stem from a Java version mismatch:
> Failed to apply plugin 'com.android.internal.application'.
> Android Gradle plugin requires Java 17 to run. You are currently using Java 11.
此错误可以通过安装并切换到请求的版本来解决。
¥This error can be resolved by installing and switching to the requested version.
上次在 Linux 上测试此演示时,该进程无法启动模拟器:
¥When this demo was last tested on Linux, the process failed to launch the emulator:
warn Please launch an emulator manually or connect a device. Otherwise app may fail to launch.
这是 React Native 中的一个已知错误!
¥This is a known bug in React Native!
如果安装了模拟器,则运行以下命令:
¥If an emulator is installed, run the following command:
npx react-native doctor
在 Android
下,会出现一个错误:
¥Under Android
, there will be one error:
Android ✖ Adb - No devices and/or emulators connected. Please create emulator with Android Studio or connect Android device.
按 f
,将显示可用模拟器的列表。选择模拟器(通常是最后一行)并按 Enter。
¥Press f
and a list of available emulators will be shown. Select the emulator
(typically the last line) and press Enter.
✔ Select the device / emulator you want to use › Emulator Pixel_3a_API_34_extension_level_7_x86_64 (disconnected)
绿色文本是虚拟设备的名称(本例中为 Pixel_3a_API_34_extension_level_7_x86_64
)。运行以下命令手动启动模拟器:
¥The text in green is the name of the virtual device
(Pixel_3a_API_34_extension_level_7_x86_64
in this example).
Run the following command to manually start the emulator:
$ANDROID_HOME/tools/emulator -avd Pixel_3a_API_34_extension_level_7_x86_64
(将 Pixel_3a_API_34_extension_level_7_x86_64
替换为虚拟设备的名称)
¥(replace Pixel_3a_API_34_extension_level_7_x86_64
with the name of the virtual device)
要确认 React Native 检测到模拟器,请再次运行以下命令:
¥To confirm React Native detects the emulator, run the following command again:
npx react-native doctor
-
打开后,该应用应类似于下面的 "之前" 屏幕截图。点击 "从电子表格导入数据" 后,验证应用是否显示新数据:
¥When opened, the app should look like the "Before" screenshot below. After tapping "Import data from a spreadsheet", verify that the app shows new data:
Before | After |
---|---|
iOS 测试
¥iOS Testing
iOS 测试只能在运行 macOS 的 Apple 硬件上执行!
¥iOS testing can only be performed on Apple hardware running macOS!
Xcode 和 iOS 模拟器在 Windows 或 Linux 上不可用。
¥Xcode and iOS simulators are not available on Windows or Linux.
-
通过从
ios
子文件夹运行pod install
来刷新 iOS 项目:¥Refresh iOS project by running
pod install
from theios
subfolder:
cd ios; pod install; cd -
-
启动 iOS 模拟器:
¥Start the iOS emulator:
npx react-native run-ios
-
打开后,该应用应类似于下面的 "之前" 屏幕截图。点击 "从电子表格导入数据" 后,验证应用是否显示新数据:
¥When opened, the app should look like the "Before" screenshot below. After tapping "Import data from a spreadsheet", verify that the app shows new data:
Before | After |
---|---|
Android 设备测试
¥Android Device Testing
-
使用 USB 电缆连接 Android 设备。
¥Connect an Android device using a USB cable.
如果设备要求允许 USB 调试,请点击 "允许"。
¥If the device asks to allow USB debugging, tap "Allow".
-
关闭所有 Android/iOS 模拟器。
¥Close any Android / iOS emulators.
-
构建 APK 并在设备上运行:
¥Build APK and run on device:
npx react-native run-android
iOS 设备测试
¥iOS Device Testing
-
使用 USB 电缆连接 iOS 设备。
¥Connect an iOS device using a USB cable.
如果设备要求信任计算机,请点击 "相信" 并输入密码。
¥If the device asks to trust the computer, tap "Trust" and enter the passcode.
-
关闭所有 Android/iOS 模拟器。
¥Close any Android / iOS emulators.
-
启用开发者代码签名证书 [^7]。
¥Enable developer code signing certificates[^7].
Enabling Code Signing (click to show)
这些说明已针对 Xcode 15.3 进行了验证。
¥These instructions were verified against Xcode 15.3.
A) 在 Xcode 中打开包含的 iOS 工作区:
¥A) Open the included iOS workspace in Xcode:
open ios/SheetJSRNFetch.xcworkspace
B) 在项目导航器中选择 "SheetJSRNFetch":
¥B) Select "SheetJSRNFetch" in the Project Navigator:
C) 在主视图中选择 "签名和能力"。
¥C) Select "Signing & Capabilities" in the main view.
D) 在下方栏中选择 "全部",并确保在下拉列表中选择了一个团队:
¥D) Select "All" in the lower bar and ensure a Team is selected in the dropdown:
-
通过 Homebrew 安装
ios-deploy
:¥Install
ios-deploy
through Homebrew:
brew install ios-deploy
-
在设备上运行:
¥Run on device:
npx react-native run-ios
在某些测试中,由于 "不受信任的开发者",应用无法在设备上运行:
¥In some tests, the app failed to run on the device due to "Untrusted Developer":
Your device management settings do not allow apps from developer ... on this iPhone. You can allow using these apps in Settings.
这些说明已针对 iOS 15.1 进行了验证。
¥These instructions were verified against iOS 15.1.
A) 打开“设置”应用并选择 "一般的" > "VPN 和设备管理"。
¥A) Open the Settings app and select "General" > "VPN & Device Management".
B) 在 "开发者应用" 下选择 Apple 开发证书。
¥B) Select the Apple Development certificate under "Developer App".
在新屏幕中,名称 "SheetJSRNFetch" 将显示在应用列表中。
¥In the new screen, the name "SheetJSRNFetch" will be displayed in the Apps list.
C) 在弹出窗口中点击 "相信 ..." 并点击 "相信"。
¥C) Tap "Trust ..." and tap "Trust" in the popup.
D) 关闭设备上的“设置”应用。
¥D) Close the Settings app on the device.
E) 关闭计算机上的 Metro 窗口。
¥E) Close the Metro window on the computer.
F) 再次运行 npx react-native run-ios
。
¥F) Run npx react-native run-ios
again.
在某些测试中,构建失败并出现以下错误:
¥In some tests, the build failed with the following error:
PhaseScriptExecution failed with a nonzero exit code
这是由于 react-native
包中的错误造成的。必须编辑脚本 node_modules/react-native/scripts/react-native-xcode.sh
。
¥This was due to an error in the react-native
package. The script
node_modules/react-native/scripts/react-native-xcode.sh
must be edited.
在脚本顶部附近,将有一个 set
语句:
¥Near the top of the script, there will be a set
statement:
# Print commands before executing them (useful for troubleshooting)
set -x -e
DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH
必须删除 -e
参数:
¥The -e
argument must be removed:
# Print commands before executing them (useful for troubleshooting)
set -x
DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH
在一些测试运行中,错误提到了开发团队:
¥In some test runs, the error mentioned a development team:
error: Signing for "SheetJSRNFetch" requires a development team. Select a development team in the Signing & Capabilities editor. (in target 'SheetJSRNFetch' from project 'SheetJSRNFetch')
必须在项目中启用代码签名!
¥Code signing must be enabled in the project!
默认情况下,React Native 生成专门针对 iPhone 的应用。在物理 iPad 上,将运行像素化的 iPhone 应用。
¥By default, React Native generates applications that exclusively target iPhone. On a physical iPad, a pixellated iPhone app will be run.
必须更改 "目标器件系列" 设置才能支持 iPad:
¥The "targeted device families" setting must be changed to support iPad:
A) 打开 Xcode 工作区:
¥A) Open the Xcode workspace:
open ./ios/SheetJSRNFetch.xcworkspace
B) 在左侧栏中选择项目:
¥B) Select the project in the left sidebar:
C) 在侧栏中选择 "SheetJSRNFetch" 目标。
¥C) Select the "SheetJSRNFetch" target in the sidebar.
D) 选择主区域中的 "构建设置" 选项卡。
¥D) Select the "Build Settings" tab in the main area.
E) 在 "构建设置" 下面的搜索栏中,输入 "tar"
¥E) In the search bar below "Build Settings", type "tar"
F) 在 "目标设备系列" 行中,将值更改为 "iPhone、iPad"。
¥F) In the "Targeted Device Families" row, change the value to "iPhone, iPad".
在某些测试运行中,构建失败并显示一条配置消息:
¥In some test runs, the build failed with a Provisioning message:
error: Provisioning profile "..." doesn't include the currently selected device "..." (identifier ...).
通过手动选择目标设备解决了这个问题:
¥This was resolved by manually selecting the target device:
A) 打开 Xcode 工作区:
¥A) Open the Xcode workspace:
open ./ios/SheetJSRNFetch.xcworkspace
B) 在左侧栏中选择项目:
¥B) Select the project in the left sidebar:
C) 在顶部栏中,项目名称旁边将有一个灰色的设备图标。单击该图标并从列表中选择真实设备。
¥C) In the top bar, next to the project name, there will be a gray device icon. Click on the icon and select the real device from the list.
本地文件
¥Local Files
React Native 不提供原生文件选择器或从设备上的文档读取和写入数据的方法。必须使用第三方库。
¥React Native does not provide a native file picker or a method for reading and writing data from documents on the devices. A third-party library must be used.
由于 React Native 内部结构在版本之间发生变化,因此库可能仅适用于特定版本的 React Native。在选择库之前应查阅项目文档。
¥Since React Native internals change between releases, libraries may only work with specific versions of React Native. Project documentation should be consulted before picking a library.
下表列出了经过测试的文件插件。"OS" 列出了经过测试的平台("A" 适用于 Android,"I" 适用于 iOS)。
¥The following table lists tested file plugins. "OS" lists tested platforms ("A" for Android and "I" for iOS).
文件系统插件 | 文件选择器插件 | OS |
---|---|---|
react-native-file-access | react-native-document-picker | AI |
react-native-blob-util | react-native-document-picker | AI |
expo-file-system | expo-document-picker | AI |
应用配置
¥App Configuration
出于隐私考虑,应用必须请求文件访问权限。有用于访问数据的特殊 API,并且在未来的平台版本中可能会发生变化。
¥Due to privacy concerns, apps must request file access. There are special APIs for accessing data and are subject to change in future platform versions.
Technical Details (click to show)
iOS
iOS applications typically require two special settings in Info.plist
:
-
UIFileSharingEnabled
[^8] allows users to use files written by the app. A special folder will appear in the "Files" app. -
LSSupportsOpeningDocumentsInPlace
[^9] allows the app to open files without creating a local copy.
Both settings must be set to true
:
<plist version="1.0">
<dict>
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
Once the options are set, generated files are visible to users and can be shared with other apps including "Mail", "Messages", and "Numbers".
Android
Permissions and APIs have evolved over time. For broadest compatibility, the
following permissions must be enabled in AndroidManifest.xml
:
-
READ_EXTERNAL_STORAGE
andWRITE_EXTERNAL_STORAGE
allow apps to access files outside of the app scope. These are required for scoped storage access. -
android:requestLegacyExternalStorage="true"
enabled legacy behavior in some older releases.
The manifest is saved to android/app/src/main/AndroidManifest.xml
:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:requestLegacyExternalStorage="true"
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme">
Depending on the Android API level, there are three strategies for writing files:
-
In "legacy" mode (supported in API levels up to 29), files can be written to the user Downloads or Documents folder directly.
-
Using the
MediaStore
API, files should be copied to a visible location. -
Using the "Storage Access Framework", the user grants access to a folder and the app uses SAF APIs to create files and write data.
RN 文件选择器
¥RN File Picker
"文件选择器" 库处理两个特定于平台的步骤:
¥The "File Picker" library handles two platform-specific steps:
-
显示允许用户从其设备中选择文件的视图
¥Show a view that allows users to select a file from their device
-
将所选文件复制到应用可以读取的位置
¥Copy the selected file to a location that can be read by the application
以下库已经过测试:
¥The following libraries have been tested:
react-native-document-picker
react-native-document-picker
提供了一种 pickSingle
方法供用户选择一个文件。
¥react-native-document-picker
provides a pickSingle
method for users to select one file.
文件插件通常需要 copyTo: "cachesDirectory"
选项:
¥The file plugins generally require the copyTo: "cachesDirectory"
option:
import { pickSingle } from 'react-native-document-picker';
const f = await pickSingle({
allowMultiSelection: false,
copyTo: "cachesDirectory",
mode: "open"
});
const path = f.fileCopyUri; // this path can be read by RN file plugins
expo-document-picker
expo-document-picker
是一个与 Expo 生态系统中其他模块配合使用的选择器。
¥expo-document-picker
is a picker
that works with other modules in the Expo ecosystem.
getDocumentAsync
方法允许用户选择文件。Expo 文件插件需要 copyToCacheDirectory
选项:
¥The getDocumentAsync
method allows users to select a file. The Expo file
plugin requires the copyToCacheDirectory
option:
import * as DocumentPicker from 'expo-document-picker';
const result = await DocumentPicker.getDocumentAsync({
copyToCacheDirectory: true,
type: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']
});
const path = result.assets[0].uri; // this path can be read by RN file plugins
RN 文件插件
¥RN File Plugins
以下库已经过测试:
¥The following libraries have been tested:
react-native-blob-util
react-native-blob-util
是 2016 年其他库的延续。
¥react-native-blob-util
is the continuation of other libraries that date back to 2016.
ascii
类型返回与原始字节对应的数字数组。数据中的 Uint8Array
与 buffer
类型兼容。
¥The ascii
type returns an array of numbers corresponding to the raw bytes.
A Uint8Array
from the data is compatible with the buffer
type.
Reading and Writing snippets (click to hide)
Reading Data
import * as XLSX from "xlsx";
import RNFetchBlob from 'react-native-blob-util';
const { readFile } = RNFetchBlob.fs;
const res = await readFile(path, 'ascii');
const wb = XLSX.read(new Uint8Array(res), {type:'buffer'});
On iOS, the URI from react-native-document-picker
must be massaged:
import { pickSingle } from 'react-native-document-picker';
import RNFetchBlob from 'react-native-blob-util';
const { readFile, dirs: { DocumentDir } } = RNFetchBlob.fs;
const f = await pickSingle({
// Instruct the document picker to copy file to Documents directory
copyTo: "documentDirectory",
allowMultiSelection: false, mode: "open" });
// `f.uri` is the original path and `f.fileCopyUri` is the path to the copy
let path = f.fileCopyUri;
// iOS workaround
if (Platform.OS === 'ios') path = path.replace(/^.*\/Documents\//, DDP + "/");
const res = await readFile(path, 'ascii');
Writing Data
import * as XLSX from "xlsx";
import RNFetchBlob from 'react-native-blob-util';
const { writeFile, readFile, dirs:{ DocumentDir } } = RNFetchBlob.fs;
const wbout = XLSX.write(wb, {type:'buffer', bookType:"xlsx"});
const file = DocumentDir + "/sheetjsw.xlsx";
const res = await writeFile(file, Array.from(wbout), 'ascii');
Sharing Files in Android
copyToMediaStore
uses the MediaStore
API to share files.
The file must be written to the device before using the MediaStore
API!
// ... continuation of "writing data"
const { MediaCollection } = RNFetchBlob;
/* Copy to downloads directory (android) */
try {
await MediaCollection.copyToMediaStore({
parentFolder: "",
mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
name: "sheetjsw.xlsx"
}, "Download", file);
} catch(e) {}
react-native-file-access
react-native-file-access
是一个使用现代 iOS 和 Android 开发模式的文件系统 API。
¥react-native-file-access
is a filesystem API that uses modern iOS and Android development patterns.
base64
编码返回与 base64
类型兼容的字符串:
¥The base64
encoding returns strings compatible with the base64
type:
Reading and Writing snippets (click to hide)
Reading Data
import * as XLSX from "xlsx";
import { FileSystem } from "react-native-file-access";
const b64 = await FileSystem.readFile(path, "base64");
/* b64 is a Base64 string */
const workbook = XLSX.read(b64, {type: "base64"});
Writing Data
import * as XLSX from "xlsx";
import { Dirs, FileSystem } from "react-native-file-access";
const DDP = Dirs.DocumentDir + "/";
const b64 = XLSX.write(workbook, {type:'base64', bookType:"xlsx"});
/* b64 is a Base64 string */
await FileSystem.writeFile(DDP + "sheetjs.xlsx", b64, "base64");
Sharing Files in Android
cpExternal
uses the MediaStore
API to share files.
The file must be written to the device before using the MediaStore
API!
// ... continuation of "writing data"
/* Copy to downloads directory (android) */
try {
await FileSystem.cpExternal(file, "sheetjsw.xlsx", "downloads");
} catch(e) {}
expo-file-system
expo-file-system
是一个文件系统 API,可与 Expo 生态系统中的其他模块配合使用。
¥expo-file-system
is a filesystem API
that works with other modules in the Expo ecosystem.
某些 Expo API 返回无法使用 expo-file-system
读取的 URI。这将表现为一个错误:
¥Some Expo APIs return URI that cannot be read with expo-file-system
. This
will manifest as an error:
位置 '...' 不受支持的方案
¥Unsupported scheme for location '...'
expo-document-picker
代码片段会生成本地副本。
¥The expo-document-picker
snippet makes a local copy.
EncodingType.Base64
编码与 base64
类型兼容。
¥The EncodingType.Base64
encoding is compatible with base64
type.
Reading and Writing snippets (click to hide)
Reading Data
Calling FileSystem.readAsStringAsync
with FileSystem.EncodingType.Base64
encoding returns a promise resolving to a string compatible with base64
type:
import * as XLSX from "xlsx";
import * as FileSystem from 'expo-file-system';
const b64 = await FileSystem.readAsStringAsync(uri, { encoding: FileSystem.EncodingType.Base64 });
const workbook = XLSX.read(b64, { type: "base64" });
Writing Data
The FileSystem.EncodingType.Base64
encoding accepts Base64 strings:
import * as XLSX from "xlsx";
import * as FileSystem from 'expo-file-system';
const b64 = XLSX.write(workbook, {type:'base64', bookType:"xlsx"});
/* b64 is a Base64 string */
await FileSystem.writeAsStringAsync(FileSystem.documentDirectory + "sheetjs.xlsx", b64, { encoding: FileSystem.EncodingType.Base64 });
Sharing Files in Android
StorageAccessFramework
uses the "Storage Access Framework" to share files.
SAF API methods must be used to request permissions, make files and write data:
import * as XLSX from "xlsx";
import { documentDirectory, StorageAccessFramework } from 'expo-file-system';
const b64 = XLSX.write(workbook, {type:'base64', bookType:"xlsx"});
/* b64 is a Base64 string */
try {
/* request access to a folder */
const perms = await StorageAccessFramework.requestDirectoryPermissionsAsync(documentDirectory);
/* if the user selected a folder ... */
if(perms.granted) {
/* create a new file */
const uri = perms.directoryUri;
const file = await StorageAccessFramework.createFileAsync(uri, "sheetjsw", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
/* write data to file */
await StorageAccessFramework.writeAsStringAsync(file, wbout, { encoding: "base64" });
}
} catch(e) {}
演示
¥Demo
本 demo 在以下环境下进行了测试:
¥This demo was tested in the following environments:
真实设备
¥Real Devices
OS | 设备 | RN | 日期 |
---|---|---|---|
iOS 15.5 | iPhone 13 Pro 最大 | 0.73.6 | 2024-03-31 |
安卓 29 | 英伟达盾 | 0.73.6 | 2024-03-31 |
模拟器
¥Simulators
OS | 设备 | RN | 开发平台 | 日期 |
---|---|---|---|---|
安卓 34 | 像素 3a | 0.73.6 | darwin-x64 | 2024-03-31 |
iOS 17.4 | iPhone 15 Pro 最大 | 0.73.6 | darwin-x64 | 2024-03-31 |
安卓 34 | 像素 3a | 0.73.6 | win10-x64 | 2024-03-31 |
安卓 34 | 像素 3a | 0.73.6 | linux-x64 | 2024-03-31 |
React Native 应用存在许多移动部件和陷阱。强烈建议在进行此演示之前先阅读适用于 iOS 和 Android 的 React Native 官方教程。[^10] 此处不介绍包括 Android 虚拟设备配置在内的详细信息。
¥There are many moving parts and pitfalls with React Native apps. It is strongly recommended to follow the official React Native tutorials for iOS and Android before approaching this demo.[^10] Details including Android Virtual Device configuration are not covered here.
此示例尝试分离特定于库的函数。
¥This example tries to separate the library-specific functions.
项目设置
¥Project Setup
-
创建项目:
¥Create project:
npx react-native init SheetJSRN --version="0.73.6"
在 macOS 上,如果提示安装 CocoaPods
,请按 y
。
¥On macOS, if prompted to install CocoaPods
, press y
.
-
安装共享依赖:
¥Install shared dependencies:
cd SheetJSRN
curl -LO https://xlsx.nodejs.cn/logo.png
npm i -S https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz
npm i -S react-native-table-component@1.2.2 react-native-document-picker@9.1.1
curl -LO https://xlsx.nodejs.cn/mobile/index.js
-
启动 Android 模拟器:
¥Start the Android emulator:
npx react-native run-android
该应用应类似于以下屏幕截图:
¥The app should look like the following screenshot:
上次在 Windows 上测试此演示时,构建失败并出现错误:
¥When this demo was last tested on Windows, the build failed with an error:
> Failed to apply plugin 'com.android.internal.application'.
> Android Gradle plugin requires Java 17 to run. You are currently using Java 11.
必须安装 Java 17[^11],并且 JAVA_HOME
环境变量必须指向 Java 17 位置。
¥Java 17 must be installed[^11] and the JAVA_HOME
environment variable must
point to the Java 17 location.
停止开发服务器并关闭 React Native Metro NodeJS 窗口。
¥Stop the dev server and close the React Native Metro NodeJS window.
文件整合
¥File Integration
-
选择一个文件系统库进行集成:
¥Pick a filesystem library for integration:
- RNBU
- RNFA
- EXPO
安装 react-native-blob-util
依赖:
¥Install react-native-blob-util
dependency:
npm i -S react-native-blob-util@0.19.8
将高亮的行添加到 index.js
:
¥Add the highlighted lines to index.js
:
import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
import { read, write } from 'xlsx';
import { pickSingle } from 'react-native-document-picker';
import { Platform } from 'react-native';
import RNFetchBlob from 'react-native-blob-util';
async function pickAndParse() {
const f = await pickSingle({allowMultiSelection: false, copyTo: "documentDirectory", mode: "open" });
let path = f.fileCopyUri;
if (Platform.OS === 'ios') path = path.replace(/^.*\/Documents\//, RNFetchBlob.fs.dirs.DocumentDir + "/");
const res = await (await fetch(path)).arrayBuffer(); // RN >= 0.72
// const res = await RNFetchBlob.fs.readFile(path, 'ascii'); // RN < 0.72
return read(new Uint8Array(res), {type: 'buffer'});
}
async function writeWorkbook(wb) {
const wbout = write(wb, {type:'buffer', bookType:"xlsx"});
const file = RNFetchBlob.fs.dirs.DocumentDir + "/sheetjsw.xlsx";
await RNFetchBlob.fs.writeFile(file, Array.from(wbout), 'ascii');
/* Copy to downloads directory (android) */
try { await RNFetchBlob.MediaCollection.copyToMediaStore({
parentFolder: "",
mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
name: "sheetjsw.xlsx"
}, "Download", file); } catch(e) {}
return file;
}
const make_width = ws => {
安装 react-native-file-access
依赖:
¥Install react-native-file-access
dependency:
npm i -S react-native-file-access@3.0.7
将高亮的行添加到 index.js
:
¥Add the highlighted lines to index.js
:
import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
import { read, write } from 'xlsx';
import { pickSingle } from 'react-native-document-picker';
import { Dirs, FileSystem } from 'react-native-file-access';
async function pickAndParse() {
const f = await pickSingle({allowMultiSelection: false, copyTo: "documentDirectory", mode: "open" });
let path = f.fileCopyUri;
const res = await (await fetch(path)).arrayBuffer();
return read(new Uint8Array(res), {type: 'buffer'});
}
async function writeWorkbook(wb) {
const wbout = write(wb, {type:'base64', bookType:"xlsx"});
const file = Dirs.DocumentDir + "/sheetjsw.xlsx";
await FileSystem.writeFile(file, wbout, "base64");
/* Copy to downloads directory (android) */
try { await FileSystem.cpExternal(file, "sheetjsw.xlsx", "downloads"); } catch(e) {}
return file;
}
const make_width = ws => {
安装 expo-file-system
和 expo-document-picker
依赖:
¥Install expo-file-system
and expo-document-picker
dependencies:
npx install-expo-modules
npm i -S expo-file-system expo-document-picker
在最近的测试中,安装过程中出现了一些问题。
¥In the most recent test, the installation asked a few questions.
如果提示更改 iOS 部署目标,请选择是。
¥If prompted to change iOS deployment target, choose Yes.
如果提示安装 Expo CLI 集成,请选择否。
¥If prompted to install Expo CLI integration, choose No.
将高亮的行添加到 index.js
:
¥Add the highlighted lines to index.js
:
import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
import { read, write } from 'xlsx';
import { getDocumentAsync } from 'expo-document-picker';
import { documentDirectory, readAsStringAsync, writeAsStringAsync, StorageAccessFramework } from 'expo-file-system';
async function pickAndParse() {
const result = await getDocumentAsync({copyToCacheDirectory: true});
const path = result.assets[0].uri;
const res = await readAsStringAsync(path, { encoding: "base64" });
return read(res, {type: 'base64'});
}
async function writeWorkbook(wb) {
const wbout = write(wb, {type:'base64', bookType:"xlsx"});
const file = documentDirectory + "sheetjsw.xlsx";
await writeAsStringAsync(file, wbout, { encoding: "base64" });
/* Write to documents directory (android) */
try {
const perms = await StorageAccessFramework.requestDirectoryPermissionsAsync(documentDirectory);
if(perms.granted) {
const uri = perms.directoryUri;
const file = await StorageAccessFramework.createFileAsync(uri, "sheetjsw", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
await StorageAccessFramework.writeAsStringAsync(file, wbout, { encoding: "base64" });
}
} catch(e) {}
return file;
}
const make_width = ws => {
安卓测试
¥Android Testing
-
重启 Android 开发进程:
¥Restart the Android development process:
npx react-native run-android
将显示以下应用:
¥The following app will be shown:
上次在 macOS 上测试此演示时,该过程无法启动模拟器:
¥When this demo was last tested on macOS, the process failed to launch the emulator:
warn Please launch an emulator manually or connect a device. Otherwise app may fail to launch.
这是 React Native 中的一个已知错误!
¥This is a known bug in React Native!
如果安装了模拟器,则运行以下命令:
¥If an emulator is installed, run the following command:
npx react-native doctor
在 Android
下,会出现一个错误:
¥Under Android
, there will be one error:
Android ✖ Adb - No devices and/or emulators connected. Please create emulator with Android Studio or connect Android device.
按 f
,将显示可用模拟器的列表。选择模拟器(通常是最后一行)并按 Enter。
¥Press f
and a list of available emulators will be shown. Select the emulator
(typically the last line) and press Enter.
✔ Select the device / emulator you want to use › Emulator Pixel_3a_API_34 (disconnected)
绿色文本是虚拟设备的名称(本例中为 Pixel_3a_API_34
)。运行以下命令手动启动模拟器:
¥The text in green is the name of the virtual device (Pixel_3a_API_34
in this
example). Run the following command to manually start the emulator:
$ANDROID_HOME/tools/emulator -avd Pixel_3a_API_34
(将 Pixel_3a_API_34
替换为虚拟设备的名称)
¥(replace Pixel_3a_API_34
with the name of the virtual device)
要确认 React Native 检测到模拟器,请再次运行以下命令:
¥To confirm React Native detects the emulator, run the following command again:
npx react-native doctor
-
下载 https://xlsx.nodejs.cn/pres.numbers 并打开下载文件夹。
¥Download https://xlsx.nodejs.cn/pres.numbers and open the Downloads folder.
-
单击
pres.numbers
并将其从“下载”文件夹拖到模拟器中。¥Click and drag
pres.numbers
from the Downloads folder into the simulator. -
单击 "导入数据" 并查找
pres.numbers
。¥Click "Import data" and look for
pres.numbers
.
如果未显示该文件,请单击 ≡
图标,然后单击 "下载"。
¥If the file is not displayed, click the ≡
icon and click "Downloads".
-
选择
pres.numbers
。¥Select
pres.numbers
.
屏幕应刷新并显示新内容:
¥The screen should refresh with new contents:
-
单击 "导出数据"。
¥Click "Export data".
Android 上的 expo-file-system
将提示授予文件夹访问权限。
¥expo-file-system
on Android will prompt to grant access to a folder.
点击 ≡
图标,然后点击带有下载图标的 "文件" 文件夹。
¥Tap the ≡
icon and tap the "Documents" folder with the download icon.
点击 'ALLOW ACCESS TO "DOCUMENTS"' 按钮。
¥Tap the 'ALLOW ACCESS TO "DOCUMENTS"' button.
在 "允许访问" 弹出窗口中,点击 "ALLOW"。
¥In the "Allow access" pop, tap "ALLOW".
警报将显示文件的位置:
¥An alert will display the location to the file:
-
从模拟器中提取文件并验证内容:
¥Pull the file from the simulator and verify the contents:
adb exec-out run-as com.sheetjsrn cat files/sheetjsw.xlsx > /tmp/sheetjsw.xlsx
npx xlsx-cli /tmp/sheetjsw.xlsx
PowerShell 会破坏重定向中的二进制数据。
¥PowerShell mangles binary data in the redirect.
在 Windows 上,必须在命令提示符中运行以下命令:
¥On Windows, the following commands must be run in the Command Prompt:
adb exec-out run-as com.sheetjsrn cat files/sheetjsw.xlsx > sheetjsw.xlsx
npx xlsx-cli sheetjsw.xlsx
-
停止开发服务器并关闭 React Native Metro NodeJS 窗口。
¥Stop the dev server and close the React Native Metro NodeJS window.
iOS 测试
¥iOS Testing
iOS 测试只能在运行 macOS 的 Apple 硬件上执行!
¥iOS testing can only be performed on Apple hardware running macOS!
Xcode 和 iOS 模拟器在 Windows 或 Linux 上不可用。
¥Xcode and iOS simulators are not available on Windows or Linux.
向下滚动至 "Android 设备测试" 以进行设备测试。
¥Scroll down to "Android Device Testing" for device tests.
-
通过从
ios
子文件夹运行pod install
来刷新 iOS 项目:¥Refresh iOS project by running
pod install
from theios
subfolder:
cd ios; pod install; cd -
-
开始 iOS 开发流程:
¥Start the iOS development process:
npx react-native run-ios
-
下载 https://xlsx.nodejs.cn/pres.numbers 并打开下载文件夹。
¥Download https://xlsx.nodejs.cn/pres.numbers and open the Downloads folder.
-
在模拟器中,单击“主页”图标返回主屏幕。
¥In the simulator, click the Home icon to return to the home screen.
-
单击 "文件" 图标打开应用。
¥Click on the "Files" icon to open the app.
-
单击
pres.numbers
并将其从“下载”文件夹拖到模拟器中。¥Click and drag
pres.numbers
from the Downloads folder into the simulator.
-
确保高亮 "在我的 iPhone 上" 并选择 "保存"。
¥Make sure "On My iPhone" is highlighted and select "Save".
-
再次单击主页图标,然后选择
SheetJSRN
应用。¥Click the Home icon again then select the
SheetJSRN
app. -
单击 "导入数据" 并选择
pres
:¥Click "Import data" and select
pres
:
选择后,屏幕应刷新并显示新内容:
¥Once selected, the screen should refresh with new contents:
-
单击 "导出数据"。
¥Click "Export data".
警报将显示文件的位置:
¥An alert will display the location to the file:
-
找到该文件并验证内容是否正确:
¥Find the file and verify the contents are correct:
find ~/Library/Developer/CoreSimulator -name sheetjsw.xlsx |
while read x; do echo "$x"; npx xlsx-cli "$x"; done
测试完成后,停止模拟器和开发过程。
¥Once testing is complete, stop the simulator and the development process.
Android 设备测试
¥Android Device Testing
-
将高亮的行添加到
android/app/src/main/AndroidManifest.xml
:¥Add the highlighted lines to
android/app/src/main/AndroidManifest.xml
:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:requestLegacyExternalStorage="true"
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme">
父 manifest
标签内将有两个新的 uses-permission
标签。属性 android:requestLegacyExternalStorage="true"
必须添加到 application
标记中。
¥There will be two new uses-permission
tags within the parent manifest
tag.
The attribute android:requestLegacyExternalStorage="true"
must be added to the
application
tag.
-
关闭所有 Android / iOS 模拟器。
¥Close any Android / iOS simulators.
停止开发服务器并关闭 React Native Metro NodeJS 窗口。
¥Stop the dev server and close the React Native Metro NodeJS window.
-
使用 USB 电缆连接 Android 设备。
¥Connect an Android device using a USB cable.
如果设备要求允许 USB 调试,请点击 "允许"。
¥If the device asks to allow USB debugging, tap "Allow".
-
构建 APK 并在设备上运行:
¥Build APK and run on device:
npx react-native run-android
-
在设备上下载 https://xlsx.nodejs.cn/pres.numbers。
¥Download https://xlsx.nodejs.cn/pres.numbers on the device.
-
切换回 "SheetJSRN" 应用。
¥Switch back to the "SheetJSRN" app.
-
点击 "导入数据",然后点击
pres.numbers
。¥Tap "Import data" and tap
pres.numbers
.
如果未显示该文件,请点击 ≡
图标,然后点击 "下载"。
¥If the file is not displayed, tap the ≡
icon and tap "Downloads".
该表将使用文件中的数据刷新。
¥The table will refresh with data from the file.
-
点击 "导出数据"。
¥Tap "Export Data".
Android 上的 expo-file-system
将提示授予文件夹访问权限。
¥expo-file-system
on Android will prompt to grant access to a folder.
点击 ≡
图标,然后点击带有下载图标的 "文件" 文件夹。
¥Tap the ≡
icon and tap the "Documents" folder with the download icon.
点击 'ALLOW ACCESS TO "DOCUMENTS"' 按钮。
¥Tap the 'ALLOW ACCESS TO "DOCUMENTS"' button.
在 "允许访问" 弹出窗口中,点击 "ALLOW"。
¥In the "Allow access" pop, tap "ALLOW".
在 exportFile
弹出窗口中点击 "OK"。
¥Tap "OK" in the exportFile
popup.
-
切换到“文件”应用并导航到“下载”文件夹。
¥Switch to the Files app and navigate to the Downloads folder.
测试 expo-file-system
时,选择 "文件"。
¥When testing expo-file-system
, select "Documents".
将有一个新文件 sheetjsw.xlsx
。
¥There will be a new file sheetjsw.xlsx
.
-
关闭并重新打开 "SheetJSRN" 应用。数据将重置。
¥Close and reopen the "SheetJSRN" app. The data will reset.
-
点击 "导入数据",然后点击
sheetjsw.xlsx
。¥Tap "Import data" and tap
sheetjsw.xlsx
.
如果未显示该文件,请点击 ≡
图标,然后点击 "下载"。
¥If the file is not displayed, tap the ≡
icon and tap "Downloads".
测试 expo-file-system
时,选择 "文件"。
¥When testing expo-file-system
, select "Documents".
该表将使用导出文件中的数据刷新。
¥The table will refresh with the data from the exported file.
iOS 设备测试
¥iOS Device Testing
-
关闭所有 Android/iOS 模拟器。
¥Close any Android / iOS emulators.
-
启用文件共享并使文档文件夹在 iOS 应用中可见。将以下行添加到
ios/SheetJSRN/Info.plist
:¥Enable file sharing and make the documents folder visible in the iOS app. Add the following lines to
ios/SheetJSRN/Info.plist
:
<plist version="1.0">
<dict>
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
(文档的根元素是 plist
,它包含一个 dict
子元素)
¥(The root element of the document is plist
and it contains one dict
child)
-
启用开发者代码签名证书。获取演示 的 "iOS 设备测试" 部分涵盖了更多详细信息(步骤 15)。
¥Enable developer code signing certificates. More details are covered in the "iOS Device Testing" part of the Fetch Demo (step 15).
-
通过 Homebrew 安装
ios-deploy
:¥Install
ios-deploy
through Homebrew:
brew install ios-deploy
-
在设备上运行:
¥Run on device:
npx react-native run-ios
如果构建失败,一些故障排除说明将包含在 获取演示 的 "iOS 设备测试" 部分中(步骤 17)。
¥If the build fails, some troubleshooting notes are included in the "iOS Device Testing" part of the Fetch Demo (step 17).
-
在设备上下载 https://xlsx.nodejs.cn/pres.numbers。
¥Download https://xlsx.nodejs.cn/pres.numbers on the device.
-
切换回 "SheetJSRN" 应用。
¥Switch back to the "SheetJSRN" app.
-
从“最近”列表中点击 "导入数据" 并点击
pres
。¥Tap "Import data" and tap
pres
from the Recents list.
该表将使用文件中的数据刷新。
¥The table will refresh with data from the file.
-
点击 "导出数据",然后在
exportFile
弹出窗口中点击 "OK"。¥Tap "Export Data" and tap "OK" in the
exportFile
popup. -
从 iOS App Store 安装 "数字" 应用。
¥Install the "Numbers" app from the iOS App Store.
-
打开 "文件" 应用。重复点击左上角的
<
按钮可返回 "浏览" 视图。¥Open the "Files" app. Repeatedly tap the
<
button in the top-left corner to return to the "Browse" view. -
点击 "在我的 iPhone 上" 或 "在我的 iPad 上"。点击列表中的 "SheetJSRN"。
¥Tap "On My iPhone" or "On My iPad". Tap "SheetJSRN" in the list.
该文件夹中的 sheetjsw
条目是生成的文件。
¥The sheetjsw
entry in this folder is the generated file.
-
按住
sheetjsw
项目直至出现菜单。选择 "分享"。¥Hold down the
sheetjsw
item until the menu appears. Select "Share". -
在共享菜单中,联系人列表下方会出现一排应用图标。向左滑动直至出现 "数字" 应用图标,然后点击该应用图标。
¥In the sharing menu, below a list of contacts, there will be a row of app icons. Swipe left until the "Numbers" app icon appears and tap the app icon.
Numbers 应用将加载电子表格,确认文件有效。
¥The Numbers app will load the spreadsheet, confirming that the file is valid.
[^1]: 按照 "React Native CLI 快速入门" 并选择适当的 "开发操作系统"。
¥Follow the "React Native CLI Quickstart" and select the appropriate "Development OS".
[^2]: 见 API 参考中的 "数组的数组"
¥See "Array of Arrays" in the API reference
[^3]: 见 "数组输出" 于 "实用函数"
¥See "Array Output" in "Utility Functions"
[^4]: 见 "数组的数组输入" 于 "实用函数"
¥See "Array of Arrays Input" in "Utility Functions"
[^5]: React-Native 提交 5b597b5
添加了 fetch
支持所需的最后一部分。从 0.72.0
开始可在正式版本中使用。
¥React-Native commit 5b597b5
added the final piece required for fetch
support. It is available in official releases starting from 0.72.0
.
[^6]: 上次测试演示时,Java 17 的 Temurin 发行版是通过运行 brew install temurin17
通过 macOS Brew 包管理器安装的。可在 adoptium.net
直接下载
¥When the demo was last tested, the Temurin distribution of Java 17 was installed through the macOS Brew package manager by running brew install temurin17
. Direct downloads are available at adoptium.net
[^7]: 请参阅 React Native 文档中的 "在设备上运行"
¥See "Running On Device" in the React Native documentation
[^8]: 请参阅 Apple 开发者文档中的 UIFileSharingEnabled
。
¥See UIFileSharingEnabled
in the Apple Developer Documentation.
[^9]: 请参阅 Apple 开发者文档中的 LSSupportsOpeningDocumentsInPlace
。
¥See LSSupportsOpeningDocumentsInPlace
in the Apple Developer Documentation.
[^10]: 遵循 Android 版 "React Native CLI 快速入门"(以及 iOS,如果适用)
¥Follow the "React Native CLI Quickstart" for Android (and iOS, if applicable)
[^11]: 请参阅 JDK 存档 for Java 17 JDK 下载链接。
¥See the JDK Archive for Java 17 JDK download links.