跳转至正文
教程

使用 React 和 Capacitor 构建移动应用

学习如何使用 React 和 Capacitor 构建移动应用,并掌握 Capgo 原生导航、过渡动画以及 iOS 布局的最佳实践。

使用 React 和 Capacitor 构建移动应用

在本教程中,我们将从一个新的 React app 开始,并使用 Capacitor 转向原生移动开发。你还可以添加 Capgo Native Navigation and Transitions 来获得原生移动体验,并使用 tailwind-capacitor 来处理安全区域。

Capacitor 可以让你轻松地将 React Web 应用转换为原生移动 app,无需进行大规模修改,也无需学习像 React Native 这样全新的技能。

只需几个简单的步骤,大多数 React 应用都能转化为移动端 App。

本教程将引导你完成整个流程:从创建一个新的 React 应用开始,随后引入 Capacitor 进军原生移动应用领域。你还可以使用 Capgo Native Navigation、Transitions 以及 tailwind-capacitor 来处理安全区域。

关于 Capacitor

CapacitorJS 彻底改变了开发体验!你可以轻松将其集成到任何 Web 项目中,它会将你的应用封装进原生 WebView,并为你生成原生的 Xcode 和 Android Studio 项目。此外,它的插件还能通过 JS bridge 调用摄像头等原生设备功能。

借助 Capacitor,你无需复杂的配置或面对陡峭的学习曲线,即可获得出色的原生移动应用。其轻量级的 API 和精简的功能让集成变得轻而易举。相信我,你会惊讶于使用 Capacitor 实现功能完备的原生 App 是如此简单!

准备你的 React App

虽然启动 React 应用有多种方法,但在本教程中,我们采用最简单的一种,即创建一个空白的 React 应用:

npx create-react-app my-app

为了创建原生移动应用,我们需要一个 export 项目。因此,让我们在 package.json 用于构建并导出 React 项目:

{
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }
}

你现在可以运行 npm run build 无需担心,并且你应该能在项目根目录下看到一个全新的 out 文件夹。

Capacitor 稍后会用到这个文件夹,但现在我们需要正确配置它。

在 React App 中添加 Capacitor

要将任何 Web App 打包进原生移动容器,我们需要先完成几个初始步骤,但之后只需执行一条 sync 命令。

首先,我们可以安装 Capacitor CLI 将其作为开发依赖安装,然后在我们的项目中配置。设置过程中,按“enter”即可接受 name 和 bundle ID 的默认值。

接下来,我们需要安装核心包以及 iOS 和 Android 平台的相关包。

最后,我们可以添加平台,Capacitor 会在项目根目录下为每个平台创建文件夹:

# Install the Capacitor CLI locally
npm install -D @capacitor/cli

# Initialize Capacitor in your React project
npx cap init

# Install the required packages
npm install @capacitor/core @capacitor/ios @capacitor/android

# Add the native platforms
npx cap add ios
npx cap add android

到这一步,你应该已经能看到新的 iOS 以及 Android React 项目中的文件夹。

这些都是真正的原生项目!

之后要访问 Android 项目,你需要安装 Android Studio 中的错误日志. 对于 iOS,你需要一台 Mac,并安装 Xcode 的 Mac.

此外,你还需要找到 capacitor.config.ts 文件,其中包含同步过程中用到的一些基础 Capacitor 设置。你唯一需要注意的是 webDir,它必须指向 build 命令的输出目录。目前该配置不正确。

要修复此问题,请打开 capacitor.config.json 文件并更新 webDir:

{
  "appId": "com.example.app",
  "appName": "my-app",
  "webDir": "out",
  "bundledWebRuntime": false
}

你可以执行以下命令来试用:

npm run build
npx cap sync

第一条命令 npm run build 只需构建你的 React 项目并导出静态构建产物即可。

而第二个命令 npx cap sync 会将所有 Web code 同步到原生平台的对应位置,以便在 App 中显示。

此外,sync 命令可能会更新原生平台并安装插件,因此当你安装新的 Capacitor 插件 现在开始运行 npx cap sync 再次尝试。

不知不觉中,你已经完成了开发,现在让我们在真机上看看 App 吧!

构建并部署原生应用

若要开发 iOS 应用,你需要准备好 Xcode 的 Mac 好,而开发 Android 应用则需要安装 Android Studio 中的错误日志 已安装。此外,如果你计划在应用商店发布应用,则需要加入 iOS 的 Apple Developer Program,并注册 Android 的 Google Play Console。

如果你刚接触原生移动开发,可以使用 Capacitor CLI 轻松打开这两个原生项目:

npx cap open ios
npx cap open android

原生项目配置好后,将应用部署到已连接的设备非常简单。在 Android Studio 中,只需等待一切就绪,无需更改任何设置即可将应用部署到已连接的设备。示例如下:

android-studio-run

在 Xcode 中,你需要配置签名账户,以便将应用部署到真机,而不只是模拟器。如果你以前没操作过,Xcode 会引导你完成整个流程(但同样需要加入 Developer Program)。之后,只需点击运行,即可在顶部选中的已连接设备上运行应用。示例如下:

xcode-run

恭喜!你已成功将 React Web 应用部署到移动设备。示例如下:

react-mobile-app

不过等等,在开发过程中其实还有一种更快捷的方法……

Capacitor 热重载

到目前为止,你可能已经习惯了现代框架自带的热重载(hot reload),好消息是,你也可以在移动设备上 实现同样的功能 而且几乎无需额外工作!

通过 live reload 访问本地托管的应用 并在你的网络中进行调试 通过让 Capacitor 应用从特定 URL 加载内容来实现。

第一步是确定你的本地 IP 地址。如果你使用的是 Mac,可以在终端运行以下命令来查看:

ipconfig getifaddr en0

在 Windows 上,运行:

ipconfig

然后找到 IPv4 地址。

我们可以通过向我们的配置中再添加一个条目,让 Capacitor 直接从服务器加载应用 capacitor.config.ts 文件:

import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.example.app',
  appName: 'my-app',
  webDir: 'out',
  bundledWebRuntime: false,
  server: {
    url: 'http://192.168.x.xx:3000',
    cleartext: true
  }
};

export default config;

请务必使用 正确的 IP 和端口,在本示例中我使用了 React 的默认端口。

现在,我们可以通过将这些更改复制到我们的原生项目中来应用它们:

npx cap copy

本指南中介绍的 copy 命令类似于 sync,但它只会 将 web 文件夹中的更改复制过来 以及配置,而不会更新 native 项目。

现在您可以通过 Android Studio 或 Xcode 再次部署您的应用。之后,如果您修改了 React 应用中的内容, App 将会自动重新加载 并显示更改!

请记住 如果你安装了新的插件(例如 camera),仍然需要重新构建你的 native 项目。这是因为 native 文件发生了变化,无法即时完成。

请注意,您应在配置中使用正确的 IP 和端口。上方的 code 代码块仅用于演示默认的 React 端口。

使用 Capacitor 插件

我们来看看如何实际使用 Capacitor 插件,前面已经提过几次。为此,可以运行以下命令安装一个相当简单的插件:

npm i @capacitor/share

这个 分享插件,但它依然会弹出原生分享对话框!现在我们只需要导入该 package,然后在应用中调用 share() 函数即可。让我们修改 src/App.js 修改为:

import React from 'react';
import { Share } from '@capacitor/share';

function App() {
  const share = async () => {
    await Share.share({
      title: 'Open Youtube',
      text: 'Check new video on youtube',
      url: 'https://www.youtube.com',
      dialogTitle: 'Share with friends'
    });
  };

  return (
    <div>
      <h1>Welcome to React and Capacitor!</h1>
      <p>
        <h2>Cool channel</h2>
        <button onClick={() => share()}>Share now!</button>
      </p>
    </div>
  );
}

export default App;

如前所述,安装新 plugin 时,我们需要执行 sync 操作,然后将应用重新部署到设备上。为此,请运行以下命令:

npx cap sync

点击按钮后,你就能看到精美的原生分享对话框弹出啦!

react-capacitor-share

接下来,你可以通过 Capgo 导航和过渡效果,让应用在 iOS 和 Android 上更具原生感,并修复会导致水平溢出或安全区域被裁剪的常见 iOS 布局问题。

使用 Capgo 原生导航与过渡效果,打造原生感 UI

我多年来一直在使用 Ionic 用于构建跨平台应用,但把它和 React 集成很 hacky,通常不值得,尤其是当你已经有了 Tailwind CSS.

若想在 React + Capacitor 应用中获得原生移动体验,请使用 Capgo plugins,而不是像 Konsta UI 这样的纯 Web UI kit:

  • @capgo/capacitor-native-navigation — 原生导航栏、iOS 上的 Liquid Glass 标签栏,以及 Android 上的毛玻璃标签栏样式。你的 React router 负责维护路由状态,插件则负责原生界面外壳。
  • @capgo/capacitor-transitions — 在 WebView 层实现 Ionic 风格的页面过渡和 iOS 边缘侧滑返回,而无需采用 Ionic UI。

安装两者:

bun add @capgo/capacitor-native-navigation @capgo/capacitor-transitions
bunx cap sync

使用 CSS inset 模式配置原生导航,让 Web 内容正确避让原生栏:

import { NativeNavigation } from '@capgo/capacitor-native-navigation';

await NativeNavigation.configure({
  contentInsetMode: 'css',
  animationDuration: 360,
  glass: {
    effect: 'liquidGlass',
  },
});

渲染 Liquid Glass 标签栏(iOS 使用系统原生渲染;Android 使用毛玻璃 WebView 背景):

await NativeNavigation.setTabbar({
  selectedId: 'home',
  labelVisibilityMode: 'labeled',
  icons: true,
  colors: { dynamic: true },
  tabs: [
    { id: 'home', title: 'Home', icon: { svg: '...' } },
    { id: 'settings', title: 'Settings', icon: { svg: '...' } },
  ],
});

await NativeNavigation.addListener('tabSelect', ({ id }) => {
  navigate(`/${id}`);
});

在应用外壳中添加原生页面转场效果:

import { useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import '@capgo/capacitor-transitions';
import { initTransitions, setDirection, setupRouterOutlet } from '@capgo/capacitor-transitions/react';

initTransitions({ platform: 'auto' });

export function AppShell() {
  const navigate = useNavigate();
  const outletRef = useRef<HTMLElement>(null);

  useEffect(() => {
    if (outletRef.current) {
      setupRouterOutlet(outletRef.current, { platform: 'auto', swipeGesture: 'auto' });
    }
  }, []);

  const openSettings = () => {
    setDirection('forward');
    navigate('/settings');
  };

  return <cap-router-outlet ref={outletRef}>{/* routes */}</cap-router-outlet>;
}

将路由页面包裹在 cap-router-outlet, cap-page,以及 cap-content,并调用 setDirection('forward') 或 setDirection('back') 导航前。当原生导航接管这些区域时,请勿重复添加 Web 页眉或页脚。

查看完整指南: 使用 @capgo/capacitor-native-navigation 以及 使用 @capgo/capacitor-transitions.

使用 Tailwind 处理安全区域

在 Tailwind CSS 中处理设备安全区域,请使用 @capgo/tailwind-capacitor (发布为 tailwind-capacitor 在 npm 上)。它提供 safe-areas 工具类和其他对 Capacitor 友好的 Tailwind 插件:

bun add -D tailwind-capacitor

在 src/index.css:

@import 'tailwindcss';
@plugin "@capgo/tailwind-capacitor/platform";
@plugin "@capgo/tailwind-capacitor/safe-areas";

使用以下工具类,例如 pt-safe, pb-safe,以及 px-safe 而不是零散地 env(safe-area-inset-*) 手动完成。项目正在积极开发中 —— 如果你的 React 环境缺少某些东西, 请在 GitHub 上提交 PR.

修复 iOS 布局问题(视口、安全区域与水平溢出)

如果 iOS 上的内容看起来被裁剪、偏移或可水平滚动,仅靠添加更多 overflow-x: hidden 或单独调整 viewport 标签通常无法解决问题。请按顺序进行以下检查。

确保 viewport meta 标签已正确应用

在……中添加 viewport meta 标签 index.html 内部 <head>:

<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />

只用一个根容器处理 iOS 安全区域

创建一个统一的 app shell,并在其中应用 safe area 内边距,而不是在多个嵌套组件中:

html,
body,
#root {
  width: 100%;
  min-height: 100%;
  margin: 0;
  padding: 0;
  overflow-x: hidden;
}

* {
  box-sizing: border-box;
}

.app-shell {
  min-height: 100dvh;
  width: 100%;
  padding-top: env(safe-area-inset-top);
  padding-right: env(safe-area-inset-right);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
}

将所有页面内容包裹在 .app-shell。在 header、modal 和 layout wrapper 中重复设置 safe-area padding 往往会让 UI 看起来被裁剪或过大。

使用 @capgo/tailwind-capacitor,你可以使用如下 utility 类来实现相同的 padding pt-safe pb-safe px-safe 在这个单一 shell 上。

设置 Capacitor iOS contentInset 改为 never 首先

在 capacitor.config.ts,建议禁用 native inset,让 CSS(或 Native Navigation 的 contentInsetMode: 'css')来管理安全区域:

const config: CapacitorConfig = {
  appId: 'com.example.myapp',
  appName: 'my-app',
  webDir: 'dist',
  ios: {
    contentInset: 'never',
  },
};

将 Capacitor 的自动 content inset 与 CSS env(safe-area-inset-*) padding 混合使用,是导致间距翻倍的常见原因。

找到真正溢出的元素

常见原因是某个元素使用了 100vw,Tailwind w-screen,固定像素宽度或较大的 min-width.

在 Safari Web Inspector 中运行:

[...document.querySelectorAll('*')]
  .filter(el => el.scrollWidth > document.documentElement.clientWidth)
  .map(el => ({
    el,
    tag: el.tagName,
    class: el.className,
    scrollWidth: el.scrollWidth,
    clientWidth: document.documentElement.clientWidth,
  }));

使用 Tailwind 时,替换 w-screen 配合 w-full 尽可能避免。许多水平溢出问题都来自 100vw / w-screen重复的 safe-area 内边距或固定宽度容器,而不是 viewport meta 标签本身。

总结

Capacitor 是基于现有 Web 项目构建原生应用的绝佳选择,它提供了一种简单的方式来共享 code 并保持一致的 UI。

此外,通过引入 Capgo,为应用添加 Live Updates 变得更加简单,确保用户始终能获得最新功能和 Bug 修复。

如果你想了解如何为 React 应用添加 Capgo,请阅读下一篇文章:

继续学习《使用 React 和 Capacitor 构建移动应用》

如果你正在使用 使用 React 和 Capacitor 构建移动应用 规划 CI/CD 自动化,并将其与 Capgo CI/CD 用于 Capgo CI/CD 中的产品工作流, Capgo Native Build 用于 Capgo Native Builds 中的产品工作流, Capgo 集成 用于 Capgo 集成中的产品工作流, CI/CD 集成 了解 CI/CD 集成的实现细节,以及 GitHub Actions 集成 有关 GitHub Actions 集成的实现细节。

为 Capacitor 应用提供 Live Updates

当 Web 层出现线上 Bug 时,通过 Capgo 发布修复,而不用等 App Store 审核好几天。用户会在后台收到更新,原生变更则仍走正常审核流程。

博客最新动态

Capgo 为你提供打造真正专业的移动应用所需的最有价值的洞察。