从双端内卷到一库多端:基于 pnpm + Taro 4 + RNOH 鸿蒙的 Monorepo 跨端架构实战

摘要 :在全渠道业务时代,团队往往需要同时维护「微信小程序/H5 裂变端」与「iOS/Android/HarmonyOS Next 高性能 App」。本文将分享一套基于 pnpm workspace 的 Monorepo 架构实战方案,通过 「UI 各自为政,业务下沉共享」 的分层思想,利用 TypeScript 接口隔离与适配器模式,实现业务契约、API 服务与状态流的高效复用。


一、 背景与痛点:多端业务的"分"与"合"

在很多中大型前端与移动端团队中,多端建设通常会经历两个极端:

  1. 极端一:各自为政,重复造轮子

    • 小程序团队用 Taro / 原生开发,App 团队用 React Native / Flutter;
    • 两端对接同一套后端 REST API,但在各自代码库里重复定义了成百上千个 TypeScript Interface、枚举、格式化函数和 API 请求方法;
    • 一旦后端字段微调或鉴权逻辑变更,极易出现**"这端修了,那端漏了"**的低级 Bug。
  2. 极端二:过度追求"一套代码通吃所有端"

    • 试图用一套跨端框架强行抹平小程序与 Native App 的 UI 和导航;
    • 结果是:在小程序上打包体积过大、分包困难;在 Native App 上手势卡顿、原生效能大打折扣,最终落入"多端不讨好"的困境。

💡 破局思路:逻辑共享,UI 隔离

经过多次架构演进,我们得出一个核心结论:

真正值得复用的是「业务契约、数据处理、状态流与后端交互逻辑」,而不是强行跨端的 UI 组件与页面路由。

基于此,我们搭建了这套基于 pnpm workspaceApp Monorepo 架构:

less 复制代码
                       ┌──────────────────────────────────────┐
                       │           后端服务 / REST API         │
                       └──────────────────┬───────────────────┘
                                          │
                                          ▼
                       ┌──────────────────────────────────────┐
                       │       packages/shared (@app/shared)  │
                       │ (业务契约 / 常量 / 纯工具 / API工厂)   │
                       └───────────┬──────────────┬───────────┘
                                   │              │
                    (HttpClient 适配)            (HttpClient 适配)
                                   │              │
                                   ▼              ▼
         ┌───────────────────────────┐          ┌───────────────────────────┐
         │       apps/miniapp        │          │       apps/rnoh-app       │
         │   Taro 4 + React + Sass   │          │  RN 0.82 + RNOH + NativeWind │
         ├───────────────────────────┤          ├───────────────────────────┤
         │   微信小程序 / H5 / 字节    │          │  iOS / Android / 纯血鸿蒙  │
         └───────────────────────────┘          └───────────────────────────┘

二、 Monorepo 整体架构设计

整个工程划分为 apps/ (应用宿主)与 packages/(公共能力包)两大维度:

less 复制代码
app-monorepo/
├── apps/
│   ├── miniapp/          # 📦 小程序/H5(Taro 4.2 + React 18 + Vite)
│   └── rnoh-app/         # 📦 跨端App(RN 0.82 + 鸿蒙RNOH 0.82 + NativeWind)
├── packages/
│   └── shared/           # 📦 零平台依赖的纯 TS 业务核心层 (@app/shared)
├── package.json          # 根工程脚本与配置
├── pnpm-workspace.yaml   # 工作区配置
└── pnpm-lock.yaml        # 统一锁定文件

多端技术栈矩阵

维度 apps/miniapp apps/rnoh-app packages/shared
目标平台 微信/抖音/支付宝小程序、H5 HarmonyOS Next、Android、iOS 跨端通用纯 TS
内核框架 Taro 4.2.1 (React 18) React Native 0.82.1 (React 19) TypeScript 5.x
鸿蒙底座 - RNOH 0.82.30 (C-API Architecture) -
UI/样式 Taro Components + Sass Ant Design RN 5 + NativeWind 4 -
状态管理 Redux / React-Redux Redux Toolkit + Redux Persist Redux Toolkit Slices
网络层实现 Taro.request (适配接口) Axios + 拦截器 (适配接口) 抽象 HttpClient 契约

三、 核心设计模式落地:如何优雅下沉业务?

1. 明确边界:什么该共享?什么该隔离?

  • ✅ 坚决下沉到 packages/shared 的内容
    • types/:统一 API 响应结构 ApiResponse<T>、分页定义 PaginatedResponse<T>、实体模型;
    • constants/:API 地址、HTTP 状态码、全局枚举;
    • utils/:时间日期格式化、树形算法、字典映射等纯函数;
    • http/:平台无关的 HttpClient 契约接口;
    • services/:工厂模式 API 服务函数;
    • store/slices/:Redux Toolkit Slices 业务状态流。
  • ❌ 严禁放入 shared 的内容
    • 平台特定的 UI(Taro <View> vs RN <View>);
    • 平台特定的路由(Taro 页面栈 vs React Navigation);
    • 平台特有的原生 API(如相机、本地文件沙箱等)。

2. 接口隔离与适配器模式:解耦网络底层

小程序端只能用 Taro.request,而 App 端基于 Axios。如何让两端共享同一套 API 请求代码?

第一步:在 shared 层定义抽象契约

typescript 复制代码
// packages/shared/src/http/types.ts
export interface RequestConfig {
  headers?: Record<string, string>;
  timeout?: number;
  noLoading?: boolean;
  params?: Record<string, unknown>;
}

export interface HttpClient {
  get<T>(path: string, config?: RequestConfig): Promise<ApiResponse<T>>;
  post<T>(path: string, body?: unknown, config?: RequestConfig): Promise<ApiResponse<T>>;
  put<T>(path: string, body?: unknown, config?: RequestConfig): Promise<ApiResponse<T>>;
  delete<T>(path: string, config?: RequestConfig): Promise<ApiResponse<T>>;
}

第二步:在 shared 层使用工厂模式封装 Service

typescript 复制代码
// packages/shared/src/services/baseService.ts
import type { HttpClient } from '../http/types';
import type { UserInfo, UploadResult, AppVersion } from '../types/api.types';

export function createBaseService(http: HttpClient) {
  return {
    getUserInfo: () => 
      http.get<UserInfo>('/sys/user/info'),
    uploadFile: (formData: FormData) =>
      http.post<UploadResult>('/file/upload', formData),
    getLastAppVersion: () =>
      http.post<AppVersion>('/sys/versionApp/getLastAppVersion'),
  };
}

第三步:各应用端提供自己的 Adapter

小程序端适配器(Taro):

typescript 复制代码
// apps/miniapp/src/adapters/http.ts
import Taro from '@tarojs/taro';
import type { HttpClient, ApiResponse } from '@app/shared';

export function createTaroHttpClient(baseURL: string): HttpClient {
  return {
    async get<T>(path, config) {
      const res = await Taro.request<ApiResponse<T>>({
        url: baseURL + path,
        method: 'GET',
        header: config?.headers,
        data: config?.params,
      });
      return res.data;
    },
    async post<T>(path, body, config) {
      const res = await Taro.request<ApiResponse<T>>({
        url: baseURL + path,
        method: 'POST',
        header: config?.headers,
        data: body,
      });
      return res.data;
    },
    // ...put, delete
  };
}

React Native / 鸿蒙端适配器(Axios):

typescript 复制代码
// apps/rnoh-app/src/adapters/http.ts
import { apiClient } from '../services/apiClient'; // 内部集成了 Token 拦截、原生 Loading
import type { HttpClient } from '@app/shared';

// 现有封装好的 Axios 实例直接契合 HttpClient 接口
export const httpClient: HttpClient = apiClient;

各端在业务层调用完全一致:

typescript 复制代码
// 在页面中无论小程序还是 App,享受完全一致的类型推导与方法调用
import { createBaseService } from '@app/shared';
const baseService = createBaseService(httpClient);

const res = await baseService.getUserInfo();
console.log(res.data.username); // 完整的 TypeScript 强类型提示!

3. 跨端状态流(Redux Slices)直接共享

由于两端均拥抱 React 生态,我们将 Redux Toolkit Slices 定义在 @app/shared 中:

typescript 复制代码
// packages/shared/src/store/slices/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import type { UserInfo } from '../../types/api.types';

export interface UserState {
  token: string | null;
  userInfo: UserInfo | null;
}

const initialState: UserState = {
  token: null,
  userInfo: null,
};

export const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    setToken: (state, action: PayloadAction<string>) => {
      state.token = action.payload;
    },
    setUserInfo: (state, action: PayloadAction<UserInfo>) => {
      state.userInfo = action.payload;
    },
    logout: (state) => {
      state.token = null;
      state.userInfo = null;
    },
  },
});

export const { setToken, setUserInfo, logout } = userSlice.actions;
export const userReducer = userSlice.reducer;
  • App 端 :引入 userReducer 并配合 redux-persist 实现 Native 原生 AsyncStorage 本地持久化;
  • 小程序端 :引入 userReducer 配合 Taro 存储,共享同一套 Action 语义。

四、 鸿蒙跨端(RNOH 0.82)深度集成与工程化细节

apps/rnoh-app 中,我们接入了 React Native 0.82(新架构 C-API)RNOH 0.82.30 ,直接面向 HarmonyOS Next(纯血鸿蒙) 进行真机编译。

1. 鸿蒙 C-API 与 NativeWind 适配

  • 样式层采用 TailwindCSS 3.4 + NativeWind 4 ,通过 react-native-css-interop 将原子类高效映射为 Native Style;

  • 鸿蒙原生代码生成(Codegen):

    bash 复制代码
    react-native codegen-harmony \
      --cpp-output-path ./harmony/entry/src/main/cpp/generated \
      --rnoh-module-path ./harmony/entry/oh_modules/@rnoh/react-native-openharmony

2. 依赖管理与补丁机制

多端开发不可避免会遇到部分 npm 包在鸿蒙端存在细微兼容性问题。我们通过 patch-package + HAR 自动重打包脚本 配合 postinstall 实现自动化治理:

json 复制代码
// apps/rnoh-app/package.json
{
  "scripts": {
    "postinstall": "patch-package && bash scripts/repack-ohos-hars.sh"
  }
}

五、 开发工作流与实战收益

1. 极简的开发体验

得益于 pnpm workspace:* 软链接机制,修改 packages/shared 代码无需经过打包发版,多端热重载即刻生效:

bash 复制代码
# 1. 根目录统一安装依赖
pnpm install

# 2. 启动微信小程序监听
pnpm dev:miniapp

# 3. 启动 React Native / 鸿蒙 Metro 调试
pnpm dev:rnoh

2. 落地成效与业务收益

对比指标 传统独立双仓库 Monorepo + 共享架构
API/类型重复率 100% 冗余编写 0 重复,单处修改全局同步
多端一致性风险 高(容易漏改、字段拼错) 极低(编译期 TS 严格校验)
新业务接入效率 小程序与 App 各写一遍业务层 仅需写一次 shared,各端只需画 UI
团队协作 跨端团队数据割裂 共享同一套业务模型与工程规范

六、 总结与展望

在移动端全渠道开发的背景下,Monorepo 不是为了"全盘大一统",而是为了"最大化复用确定性逻辑,最大化保留端原生优势"

通过这套架构:

  1. 我们让 @app/shared 保持极致纯粹(纯 TypeScript、零宿主耦合);
  2. 借助 HttpClient 适配器模式 隔离底层网络库差异;
  3. miniapprnoh-app 各自发挥 Taro 与 React Native/RNOH 在宿主环境下的极致性能。

如果你也在面临小程序与跨端 App(尤其是近期备受关注的鸿蒙 Next)的协同开发挑战,不妨尝试这套架构方案!


📦 完整开源项目地址GitHub - kuma0605/app-monorepo (欢迎 Star ⭐️ 支持)
💬 欢迎在评论区讨论:你们团队在处理多端业务(小程序 / iOS / Android / 鸿蒙)时采用了怎样的架构方案?遇到了哪些跨端痛点?

相关推荐
光影少年1 小时前
react navite 长列表优化:FlatList / SectionList 原理与优化属性
前端·react native·react.js
cindershade1 小时前
鼠标 3D 倾斜卡片
前端
子兮曰1 小时前
DeepSeek Harness 架构深潜:一个把 Agent 运行时做成纯插件树的开源 Harness
前端·后端·deepseek
子兮曰2 小时前
AI Agent 完整入门指南:从 LLM 到生产落地的 30+ 个核心概念
前端·后端·agent
楚宝ovo2 小时前
新增一个 demo 子应用 - 微前端
前端
JarvanMo3 小时前
Dart 3.13:小版本,大动作
前端
纯粹的热爱3 小时前
Ubuntu 安装 Node.js 22.x(通过 NodeSource 官方源)
前端
Highcharts3 小时前
如何选择正确的图表:驱动理解和洞察力的三部分框架(第3部分)
前端·数据可视化
JavaGuide3 小时前
Github 史诗级故障,与此同时,Cursor 版「GitHub」正式上线!
前端·后端