PAYATHON 2026

如何使用 react 和 react-native 设计跨平台插件化软件?

技术老齐

可以实现。面向第三方开发者的插件市场,建议由宿主内置跨平台组件库,插件通过接口返回 UI 描述和数据。插件便可以在宿主已有能力范围内更新页面,无需重新发布 Android、iOS 或 Web 应用。

接口也可以返回 JavaScript 模块,但宿主需要额外处理代码加载、依赖兼容、权限隔离,并满足应用商店规则,不能按普通的 React 组件请求来处理。

跨平台 UI-kit 有哪些选择?

支持 Android、iOS 和 Web 的组件库可以考虑以下几种:

组件库跨平台方式适合的需求
React Native Elements官方明确支持 Android、iOS 和 Web使用现成的通用组件构建界面
Tamagui为 React Native 和 Web 提供组件、主题及样式系统需要统一设计规范,并适配不同屏幕
React Native Paper提供通过 React Native for Web 使用的文档希望采用 Material Design 风格

支持范围可查阅 React Native Elements 官网、Tamagui 文档和 React Native Paper 的 Web 指南。

“Elements 只支持 Android 和 iOS”这一判断需要修正。选型时,仍应核对所选版本与 React、React Native、Expo 的兼容要求,并验证实际会用到的组件。

跨平台支持还需要区分两种情况:

  • React Native for Web 将兼容的 React Native 组件和 API 映射到浏览器。
  • 基于 DOM 的 React 组件库不会因此自动变成原生组件库。

React Native for Web 使用 React DOM 实现浏览器渲染;React DOM API 本身不适用于 React Native。Polaris 一类组件库的设计规范可以作为参考,但依赖 DOM 的具体组件需要通过 Web 页面承载,或者另外实现原生版本。

建议在宿主中封装自己的组件接口,例如 PluginButton、PluginForm、PluginCard。插件依赖这套稳定接口,宿主负责维护底层组件库。

插件接口能直接返回 React 组件吗?

普通 JSON 接口不能直接传输可执行的组件函数。例如,下面的组件包含 JavaScript 逻辑,无法通过 JSON.stringify() 完整传给另一端:

function PluginPanel() {
  return <Button title="刷新" onPress={() => refreshData()} />;
}

接口可以返回源代码或编译后的模块,宿主加载并执行代码后才能得到组件。这需要处理:

  • JSX、模块格式和平台差异。
  • React、UI-kit 及其他依赖的版本兼容。
  • 插件能够访问哪些宿主数据和设备能力。
  • 加载失败、运行异常和版本回滚。

React.lazy 可以在模块加载后取得组件,但不提供插件下载协议或安全沙箱。普通 React Native 应用也不能直接照搬浏览器加载远程脚本的方式。

可选方案包括:

方案插件提供的内容无需更新宿主的边界
声明式 UIJSON 组件树、数据、动作描述可以组合宿主已经支持的组件和动作
Web 插件独立 Web 页面Web 使用 iframe,移动端使用 WebView,界面在网页环境中渲染
远程 JavaScript编译后的插件代码需要专门的加载机制,且受运行时和分发规则约束
内置插件随宿主构建的组件模块可以动态启用已有插件,新增代码通常需要更新

表单、设置页、列表和业务卡片通常可以用声明式 UI 实现。需要复杂编辑器或高度自由的布局时,可以再提供 Web 插件入口。

推荐架构:插件描述界面,宿主负责渲染

1. 定义插件清单和扩展位置

先规定插件可以出现在哪里,例如详情页卡片、设置页面或工具栏操作,避免让插件任意修改宿主组件树。

插件清单至少应包含:

  • 插件 ID 和版本。
  • 支持的 UI 协议版本。
  • 页面入口或 UI 描述接口。
  • 申请的数据权限。
  • 所需的宿主能力。

这些字段属于平台自行定义的协议,React 或 Expo 没有内置这套标准。宿主应在安装时检查兼容性,并保存用户或管理员授予的权限。

声明式插件的“安装”可以只包含建立安装记录、授权和注册入口,不必下载可执行代码。

2. 定义可序列化的 UI 协议

插件接口可以返回这样的 UI 描述:

{
  "schemaVersion": 1,
  "root": {
    "id": "summary",
    "type": "stack",
    "children": [
      {
        "id": "message",
        "type": "text",
        "value": "当前有 3 个待处理任务"
      },
      {
        "id": "refresh-button",
        "type": "button",
        "label": "刷新",
        "action": "refresh"
      }
    ]
  }
}

schemaVersion: 1 是示例协议版本。宿主负责解释 stack、text 和 button;refresh 是动作标识,插件没有传入函数。

正式协议还可以增加表单字段、加载状态和主题 token。属性应有明确的边界,例如 tone: "danger",避免开放任意样式或表达式。

3. 在宿主实现跨平台渲染器

渲染器可以使用 React Native 基础组件,Web 端通过 React Native for Web 渲染。以下示例只包含渲染逻辑,实际项目中可以换成统一 UI-kit 的封装组件。

import type { ReactElement } from "react";
import { Button, Text, View } from "react-native";

type UiNode =
  | {
      id: string;
      type: "text";
      value: string;
    }
  | {
      id: string;
      type: "button";
      label: string;
      action: string;
    }
  | {
      id: string;
      type: "stack";
      children: UiNode[];
    };

type Props = {
  node: UiNode;
  onAction: (action: string) => void;
};

export function PluginUI({
  node,
  onAction,
}: Props): ReactElement {
  switch (node.type) {
    case "text":
      return <Text>{node.value}</Text>;

    case "button":
      return (
        <Button
          title={node.label}
          onPress={() => onAction(node.action)}
        />
      );

    case "stack":
      return (
        <View>
          {node.children.map((child) => (
            <PluginUI
              key={child.id}
              node={child}
              onAction={onAction}
            />
          ))}
        </View>
      );

    default:
      return <Text>暂不支持此组件</Text>;
  }
}

网络返回值必须先经过运行时校验,再交给这个组件。TypeScript 类型声明不会验证 JSON。可以使用 JSON Schema、Zod 等工具检查协议版本、节点类型和属性,同时限制树的深度、节点数量与字符串长度。

宿主提供 onAction,并将它绑定到当前安装实例。执行动作前,宿主先检查是否允许,再请求后端网关。插件返回的 JSON 不应自行决定使用哪个用户身份或访问哪个内部接口。

例如,刷新流程可以是:

用户点击按钮
  → 宿主提交 refresh 动作
  → 网关校验用户、安装实例和权限
  → 插件后端处理请求
  → 返回新的页面数据
  → 宿主更新 UI 描述并重新渲染

如果开发者需要用 React 编写插件界面,可以提供 JSX 风格的 SDK,由 SDK 的构建工具或专用运行时生成 UI 描述。这部分需要额外实现,任意 React 组件无法自动转换为这种协议。

后端如何共享数据?

建议通过受控 API 共享业务数据,由核心后端负责访问控制和数据校验。

请求关系可以设计为:

插件后端 → 核心 API → 核心业务数据
宿主前端 → 插件网关 → 插件后端

OAuth 可以用于委托授权,但仍需明确授权范围、租户归属、资源权限和撤销机制。例如,插件获准读取当前租户的任务,并不代表它可以修改任务或读取其他租户的数据。

插件自己的数据可以放在独立存储中,通过核心资源 ID 建立关联。需要同步时,通过 API 或经过鉴权的 Webhook 传递事件。长期凭证和 client_secret 应保存在后端,不能打包进 Web 或移动端代码。

什么时候使用 WebView 或远程代码?

如果插件需要自行编写完整的 React 页面,可以在 Web 端使用 iframe,在移动端使用 WebView。这种方式能够复用 Web UI,包括依赖 DOM 的组件库,但需要适配页面导航、键盘、尺寸和无障碍体验。

宿主与页面之间应只开放有限的消息协议。浏览器端要验证 postMessage 的来源和发送窗口;移动端要限制加载地址、页面跳转及桥接消息。不要把宿主的通用网络请求能力或长期令牌直接交给插件页面。

远程 JavaScript 更适合经过审核、与宿主紧密配合的插件。即使使用 Expo 的更新机制,更新内容也必须与已安装的原生运行时兼容。如果插件新增了宿主尚未包含的原生模块,就需要重新构建应用,具体限制见 Expo 的运行时版本文档。

移动端分发也需要提前验证。Apple 的审核指南限制下载并执行会改变应用功能的代码,同时在第 4.7 节规定了部分迷你应用和插件的允许范围及附加要求。采用 WebView、JSON UI 或远程 JavaScript,都不能单独保证通过审核,需要根据实际功能核对 Apple App Review Guidelines 中的适用条款。

落地时需要保留哪些边界?

  • UI 协议和插件业务版本分别管理。旧宿主遇到不支持的组件时,应显示兼容提示。
  • 前端按钮是否可见不代表权限有效,每次后端操作仍需鉴权。
  • 动作采用白名单,避免在 UI 描述中引入 eval、任意 JavaScript 表达式或任意请求地址。
  • 为插件提供请求超时、错误展示、缓存和停用机制。服务端异常可以通过错误状态处理,渲染异常可以用 Error Boundary 限制影响范围。
  • “无需更新宿主”适用于已有能力的重新组合。新增组件实现、设备能力或原生依赖,仍可能需要发布宿主更新。

备注:内容仅供参考。