一、项目概览
app-account-monorepo 是 账号维护主线业务的 monorepo 项目,采用 pnpm workspace + Turborepo 组织架构。
| 属性 | 内容 |
|---|---|
| 项目名称 | app-account-monorepo |
| 描述 | 账号维护主线业务的应用项目仓库 |
| 包管理器 | pnpm (v7/v8) |
| 工程化方案 | pnpm workspace + Turborepo |
| 语言 | TypeScript (strict 模式) |
| 内部注册源 | http://admin.npm.oppoer.me |
二、整体技术栈一览
| 分类 | 技术栈 | 说明 |
|---|---|---|
| 包管理 | pnpm workspace | monorepo 包管理 |
| 任务编排 | Turborepo | 并行构建/任务执行 |
| 编程语言 | TypeScript + React | 全线 TypeScript 严格模式 |
| UI 框架 | React 18(主)/ React 17(个别遗留应用) | 组件化开发 |
| 构建工具 | Vite 4.x(主)+ @uc/scripts + Next.js 13 | 多数应用使用 Vite |
| CSS 方案 | Sass/SCSS + CSS Modules | 模块化样式隔离 |
| 状态管理 | Mobx 6+(主)+ mobx-persist-store | 响应式状态管理 |
| 路由 | react-router-dom v6 / Next.js Router | SPA 路由 |
| UI 组件库 | @uc/color + @uca/colorui(OPPO 内部库) | 企业内部设计系统 |
| HTTP 请求 | axios + fetch | API 通信 |
| 动画 | @react-spring/web + motion | 交互动画 |
| 测试 | jest + @testing-library/react + @testing-library/jest-dom | 单元测试 |
| 代码检查 | ESLint + Prettier | 代码规范 |
| 安全验证 | @uca-internal/safe-captcha | 内部安全验证码方案 |
| 国际化 | 自有 i18n 方案 | 多语言支持 |
| 其他 | sentry + @uca-internal/cloud-observation | 错误监控与埋点 |
三、项目架构详解
3.1 Workspace 布局
app-account-monorepo/
├── apps/ # 12 个应用
│ ├── account-web/ # 账号 Web 登录注册流程应用 (Vite)
│ ├── account-auth/ # 账号 APP 端零散的 H5 页面 (@uc/scripts)
│ ├── account-external-sdk/# 帐号外发 SDK Hybrid APP (@uc/scripts, React 17)
│ ├── account-auth-app-sdk/# 账号 App 授权 SDK 页面 (Vite)
│ ├── account-auth-web-sdk/# 账号 Web 授权 SDK 项目 (Vite)
│ ├── identify-engine-hybrid/ # 账号 Hybrid 核身引擎 (Vite)
│ ├── identify-engine-web/ # 账号 Web 核身引擎 (Vite)
│ ├── complete-information-system/ # 补全信息应用 (Vite)
│ ├── security-center/ # 账号 App 安全中心 (Vite)
│ ├── user-center/ # 账号 Web 个人中心 (Vite)
│ ├── user-info/ # 账号 App 个人中心 (Vite)
│ └── information/ # 账号业务信息展示 (Next.js 13)
├── packages/ # 22 个内部共享包
│ ├── @completion-internal/mobx-domain/ # 补全流程 Mobx 领域模型
│ ├── @ideg-internal/mobx-domain/ # 核身引擎 Mobx 领域模型
│ ├── @ideg-internal/react-composables/ # 核身引擎 React 组合能力
│ ├── @uca-internal/common-components-react/ # 通用 React 组件
│ ├── @uca-internal/* (多个 Vite 插件) # 内部 Vite 插件
│ └── ...其他工具包
└── pnpm-workspace.yaml # workspace 声明
3.2 各应用构建方式
| 应用 | 构建工具 | React 版本 | 说明 |
|---|---|---|---|
| account-web | Vite 4.x | React 18 | 核心登录注册应用 |
| account-auth | @uc/scripts | React 18 | APP 端 H5 页面,旧构建系统 |
| account-external-sdk | @uc/scripts | React 17 | 唯一仍使用 React 17 的应用 |
| account-auth-app-sdk | Vite 4.x | React 18 | APP 授权 SDK |
| account-auth-web-sdk | Vite 4.x | React 18 | Web 授权 SDK |
| identify-engine-hybrid | Vite 4.x | React 18 | Hybrid 核身引擎 |
| identify-engine-web | Vite 4.x | React 18 | Web 核身引擎 |
| complete-information-system | Vite 4.x | React 18 | 信息补全 |
| security-center | Vite 4.x | React 18 | 安全中心 |
| user-center | Vite 4.x | React 18 | Web 个人中心 |
| user-info | Vite 4.x | React 18 | APP 个人中心 |
| information | Next.js 13 | React 18 | 唯一使用 Next.js 的应用 |
3.3 内部包(packages)分类
| 类别 | 包名 | 说明 |
|---|---|---|
| 状态管理 | @completion-internal/mobx-domain | 补全流程 Mobx 领域模型 |
| @ideg-internal/mobx-domain | 核身引擎 Mobx 领域模型 | |
| React 工具 | @ideg-internal/react-composables | 核身引擎 React Hook 组合能力 |
| 组件库 | @uca-internal/common-components-react | 通用 React 组件 |
| @uca-internal/safe-captcha-react | 安全验证码 React 组件 | |
| SDK | @uca-internal/safe-captcha-sdk | 安全验证码 SDK |
| @uca-internal/complete-info-sdk | 补全信息 SDK | |
| 工具库 | @uca-internal/is-type | 类型判断 |
| @uca-internal/mini-scheduler | 迷你调度器 | |
| @uca-internal/singleton-container | 单例容器 | |
| @uca-internal/app-links | 应用链接 | |
| @uca-internal/anchor-behavior-delegator | 锚点行为委托 | |
| 监控 | @uca-internal/cloud-observation | 云观测 |
| 共享配置 | @uca-internal/shared-business-config | 共享业务配置 |
| Token 管理 | @uca-internal/processtoken-manager | ProcessToken 管理 |
| @uca-internal/update-ptmanager-interceptor | PT Manager 拦截器 | |
| CSS | @uca-internal/normalize-css | CSS 归一化 |
| Vite 插件 | @uca-internal/vite-plugin-* (7个) | 内部 Vite 构建插件 |
四、构建工具详解
4.1 Vite 4.x 配置
Vite 是项目主要的构建工具,配置了以下关键插件:
- @vitejs/plugin-react — 提供 React Fast Refresh
- @vitejs/plugin-legacy — 兼容 ES5 旧浏览器
- @uca-internal/vite-plugin-auto-entry — 自动入口文件
- @uca-internal/vite-plugin-auto-mock-import — 自动 Mock 导入
- @uca-internal/vite-plugin-history-fallback — History Fallback
- @uca-internal/vite-plugin-html-folder — HTML 文件夹支持
- @uca-internal/vite-plugin-inject-idcconfig — 注入 IDC 配置
- @uca-internal/vite-plugin-resource-restore — 资源恢复
- rollup-plugin-visualizer — 构建分析可视化
4.2 CSS 预处理
- Sass/SCSS — 主要 CSS 预处理器
- CSS Modules — 使用
.module.scss文件实现样式隔离 - classnames — 条件样式拼接
五、Mobx 状态管理(重点)
5.1 使用概况
Mobx 是本项目唯一的正式状态管理方案,未被使用的方案包括 Redux、Zustand、Valtio、Jotai 等。
| 统计项 | 数值 |
|---|---|
导入 mobx 文件数 | ~56 个 |
导入 mobx-react 文件数 | ~149+ 个 |
使用 mobx-react-lite | 0 个 |
使用 mobx-state-tree | 0 个 |
| Observable 定义 | ~42 个文件 |
| Action 定义 | ~137 个文件 |
| Computed 定义 | ~20 个文件 |
| Reaction 使用 | ~11 个文件 |
| Autorun 使用 | ~11 个文件 |
5.2 使用的 Mobx 包
| 包名 | 用途 |
|---|---|
| mobx | 核心库:observable、action、computed、reaction、autorun |
| mobx-react | React 集成:observer HOC、Observer 组件 |
| mobx-persist-store | 状态持久化:makePersistable、clearPersistedStore、isHydrated |
5.3 Mobx 全局配置
所有应用统一采用 Mobx 严格模式配置:
const mobxStrictModeConfig = {
useProxies: "ifavailable", // 优先使用 Proxy 实现
enforceActions: "always", // 强制所有状态修改必须通过 action
computedRequiresReaction: true, // computed 必须在 reactive 上下文中使用
observableRequiresReaction: true, // observable 读取必须在 reactive 上下文中
reactionRequiresObservable: true, // reaction 必须依赖 observable
}
该配置确保了:
- 状态修改必须通过 action,不能在 action 外修改 observable
- 计算属性必须被响应式使用,避免不必要的计算
- 所有响应式依赖关系清晰可追踪
5.4 核心 API 用法
5.4.1 makeObservable — 声明响应式成员
使用 Mobx 6+ 推荐的 makeObservable API,在构造函数中显式声明成员类型:
import { makeObservable, observable, action, computed } from "mobx"
class ExampleStore {
// 响应式状态
count: number = 0
name: string = ""
// 计算属性
get greeting(): string {
return `Hello, ${this.name}!`
}
constructor() {
makeObservable(this, {
count: observable, // 可观察状态
name: observable, // 可观察状态
greeting: computed, // 计算属性
increment: action, // 修改状态的动作
setName: action, // 修改状态的动作
})
}
increment() {
this.count++
}
setName(name: string) {
this.name = name
}
}
注:极少数简单 Store 使用
makeAutoObservable(如OneplusAuthorizeEntryStore),它会自动推断所有属性的响应式类型。
5.4.2 computed — 派生计算属性
基于 observable 自动缓存的派生值,仅在依赖变化时重新计算:
// RegionSwitchStore 中的 computed 示例
get checkBirthdayRequired(): boolean {
if (!this.baseInfo.birthday) return true
const { countryCode } = this.currCoutryItem
const minAge = this.minorRestrictionMap[countryCode] || this.minorRestrictionMap.default
const requiredAge = Number(minAge)
const age = dayjs(this.baseInfo.birthday).diff(dayjs(), "year")
return age < requiredAge
}
5.4.3 action — 状态修改操作
所有状态修改都封装在 action 中,符合 Mobx 严格模式要求:
// action 示例
setCurrCountryItemAction(country: Country): void {
this.currCoutryItem = country
this.birthday = ""
this.baseInfo = {}
this.expectedMigrationTime = 0
this.processToken = null
}
异步 action 处理模式:
async fetchDataAction(): Promise<void> {
try {
const result = await api.fetchSomeData()
runInAction(() => {
this.data = result // 异步回调中通过 runInAction 修改状态
})
} catch (error) {
runInAction(() => {
this.error = error
})
}
}
5.4.4 reaction / autorun — 响应式副作用
用于在特定状态变化时执行副作用逻辑:
// reaction 示例:监听特定 observable 变化时执行
reaction(
() => this.currCoutryItem,
(newCountry) => {
// 国家切换时自动更新依赖数据
this.fetchCountryInfo(newCountry.countryCode)
}
)
// autorun 示例:自动追踪所有依赖,变化时重新执行
autorun(() => {
if (this.userInfo && this.isLoggedIn) {
console.log(`用户 ${this.userInfo.name} 已登录`)
}
})
5.5 Mobx 持久化方案 — mobx-persist-store
项目使用 mobx-persist-store 实现 Store 状态的本地持久化,典型配置:
import { makePersistable, clearPersistedStore, isHydrated } from "mobx-persist-store"
import { makeObservable, observable, action, computed } from "mobx"
class RegionSwitchStore {
// ... 字段定义
constructor() {
makeObservable(this, { /* ... */ })
this.initializePersistence()
}
private initializePersistence(): void {
makePersistable(this, {
name: "RegionSwitchStore_UC", // 存储 key
properties: [ // 持久化白名单
"currCountryItem",
"preCheckTicket",
"verificationId",
"bindPhoneForm",
"conflictAccountInfo",
],
storage: inOnlineEnv
? encryptedStorage // 线上使用加密存储
: window.localStorage, // 开发环境使用 localStorage
expireIn: 60 * 60 * 1000, // 60 分钟过期
removeOnExpiration: true, // 过期自动清除
})
}
// 检查持久化是否已恢复
get isHydrated(): boolean {
return isPersistStoreHydrated(this)
}
// 手动清理持久化数据
clearPersistedData(): Promise<void> {
this.currCoutryItem = DEFAULT_COUNTRY
this.birthday = ""
// ... 重置所有字段
return clearPersistedStore(this)
}
}
持久化设计要点:
- 采用白名单机制,只持久化需要跨页面/跨会话保持的状态(如用户选中的国家、表单进度)
- 区分"持久化字段"(用户进度相关)和"非持久化字段"(页面流转状态)
- 支持过期时间(通常为 60 分钟)
- 线上环境使用
encryptedStorage(加密存储),开发环境使用localStorage - 通过
isHydratedcomputed 属性判断持久化数据是否已恢复,用于 UI 条件渲染
5.6 Store 架构模式
项目在 3 个主要应用和 2 个共享包中共有约 20+ 个 Mobx Store,主要采用以下架构模式:
模式一:DI + 执行器组合模式(主力模式)
代表 Store:AuthProcessDomainStore(account-web)、IdentifyEngineProcessDomainStore(ideg-mobx-domain)
Store
├── 响应式状态(observable)
├── 计算属性(computed)
├── 状态修改(action)
├── 领域 API(依赖注入)
└── 执行器/引擎(子模块,持有 host 引用)
├── Executor 1(如 Email 执行器)
├── Executor 2(如 Mobile 执行器)
└── Executor 3(如 Password 执行器)
执行器(Executor)本身也使用 Mobx,内部包含自己的 observable 状态和 computed 属性,通过 host 引用与父 Store 通信。
关键实现:
class CompletionDomainStore {
// 子执行器
readonly completionExecutor: HybridSupportExecutor
constructor() {
this.completionExecutor = this.generateCompletionExecutor()
makeObservable(this, { /* ... */ })
}
// 注入依赖(API 仓库)
inject(deps: StoreDepends): void {
this.deps = deps
}
}
// 执行器内部也使用 Mobx
class RegisterExecutor {
@observable currCoutryItem: Country
@computed get ifDisplayedBirthday(): boolean { /* ... */ }
}
模式二:单例共享 Store 模式
代表 Store:UserStore(user-center)
通过惰性单例确保全局唯一实例,供多个业务 Store 共享数据:
// singleton.ts
let globalUserStore: UserStore | null = null
export function initializeUserStore(): UserStore {
if (!globalUserStore) {
globalUserStore = new UserStore()
}
return globalUserStore
}
UserCenterDomainStore 通过构造函数注入 UserStore,并通过 computed 代理访问共享数据:
class UserCenterDomainStore {
private userStore: UserStore
constructor(userStore: UserStore) {
this.userStore = userStore
this.modules = this.generateModuleExecutors() // 组合子模块
makeObservable(this, { /* ... */ })
}
// 从 UserStore 代理数据
get userInfo() {
return this.userStore.userInfo
}
}
模式三:模块化领域 Store 模式
代表 Store:UserCenterDomainStore(user-center)
UserCenterDomainStore
├── Modules(业务模块执行器)
│ ├── PERSONAL_INFO → PersonalInfoModule
│ ├── ACCOUNT_SECURITY → AccountSecurityModule
│ ├── DATA_MANAGEMENT → DataManagementModule
│ ├── DEVICE_MANAGEMENT → DeviceManagementModule
│ └── ABOUT → AboutModule
└── 依赖 SingleUserStore(单例)
模式四:独立功能 Store 模式
代表 Store:RegionSwitchStore、EmailBindingStore、MobileBindingStore 等
每个功能模块有自己的独立 Store,包含完整的 observable、action、computed、持久化配置。Store 之间通过 UserStore 单例间接通信。
user-center Stores 一览:
UserStore(单例,共享用户数据)
├── UserCenterDomainStore(页面主 Store,组合模块)
├── RegionSwitchStore(地区切换,独立持久化 60min)
├── EmailBindingStore(邮箱绑定,独立持久化 60min)
├── MobileBindingStore(手机绑定,独立持久化 60min)
├── EmergencyContactStore(紧急联系人,独立持久化 60min)
├── RealNameAuthStore(实名认证,独立持久化 60min)
├── ScancodeLoginStore(扫码登录)
├── FreezeOrThawStore(冻结/解冻)
├── AccountDeletionStore(账号注销)
├── ChildAccountStore(儿童账号)
└── RetrievePasswordStore(找回密码)
5.7 Mobx 与 React 的连接
项目通过 observer HOC 和 React Context 将 Mobx Store 注入到 React 组件中。
Provider 模式(示例来自 user-center):
import { createContext, useContext } from "react"
import { observer } from "mobx-react"
import { RegionSwitchStore } from "./stores/region-switch"
const RegionSwitchContext = createContext<RegionSwitchStore | null>(null)
// 自定义 Hook:消费 Context 中的 Store
export function useRegionSwitchStore(): RegionSwitchStore {
const store = useContext(RegionSwitchContext)
if (!store) throw new Error("RegionSwitchContext not provided")
return store
}
// Provider 组件
export function RegionSwitchProvider({ children, store }) {
return (
<RegionSwitchContext.Provider value={store}>
{children}
</RegionSwitchContext.Provider>
)
}
// 在组件中使用 Mobx Store,通过 observer HOC 实现响应式
const RegionSwitchPage = observer(() => {
const store = useRegionSwitchStore()
return (
<div>
<span>当前地区: {store.currCoutryItem.countryName}</span>
<button onClick={() => store.setCurrCountryItemAction(newCountry)}>
切换地区
</button>
</div>
)
})
直接在组件内使用 Store(适用于非嵌套 Provider 的场景):
import { observer } from "mobx-react"
import { userStore } from "./stores/user"
const UserProfile = observer(() => {
// 直接读取 Store 数据,observer 自动追踪依赖
return <div>{userStore.userInfo?.nickname}</div>
})
响应式渲染流程:
State Change (action)
↓
Mobx notifies observer
↓
React re-renders affected components (选择性重渲染)
↓
Components read latest observable values
↓
UI updated
5.8 Mobx 设计最佳实践(来自本项目)
- 所有状态修改必须经过 action — 通过
enforceActions: "always"保证 - 持久化与运行时状态分离 — 在 Store 中明确区分需要持久化的字段和运行时临时字段
- 计算属性替代手动派生 — 使用
computed而非在渲染函数中手动计算 - 执行器模式解耦 — 复杂业务逻辑封装到 Executor 中,Store 仅关注状态管理
- 单例共享 vs 独立 Store — 全局数据用单例,功能模块用独立 Store,按需选择
- 严格模式保障可维护性 — 通过 Mobx configure 开启所有严格选项,及早发现问题
六、其他关键依赖
6.1 路由方案
// react-router-dom v6
"react-router-dom": "^6.x"
主流应用使用 react-router-dom v6 提供的声明式路由,information 应用使用 Next.js 13 内置文件路由。
6.2 网络请求
- axios — 主要 HTTP 客户端
- fetch — 部分场景使用原生 fetch
6.3 UI 组件体系
- @uc/color — OPPO 内部设计系统组件库(基础 UI)
- @uca/colorui — 基于 @uc/color 的上层业务 UI 封装
6.4 动画方案
- @react-spring/web — 在 account-web、account-external-sdk 中使用
- motion — 在 account-web、user-center 中使用
6.5 测试体系
{
"jest": "^29.x",
"@testing-library/react": "^14.x",
"@testing-library/jest-dom": "^6.x"
}
6.6 代码规范
- ESLint — 代码静态检查(配置在
apps/*/.eslintrc.cjs) - Prettier — 代码格式化
6.7 安全验证
- @uca-internal/safe-captcha 系列包 — 提供安全验证码能力
七、技术栈特点总结
- 统一的 React 18 技术体系 — 除一个遗留应用外,全线使用 React 18 + TypeScript strict 模式
- Mobx 作为唯一状态管理方案 — 采用 Class-based Store + makeObservable 模式,配合 mobx-persist-store 实现会话级持久化
- 领域驱动设计(DDD) — Store 按照业务领域划分,通过 DI + 执行器模式组织复杂业务逻辑
- 内部生态丰富 — 大量使用 OPPO 内部 npm 包(UI 组件库、Vite 插件、工具库)
- 构建方案分化 — 主流使用 Vite 4.x,少量旧应用使用 @uc/scripts,一个应用使用 Next.js 13
- 严格的 Mobx 规范 — 开启所有严格模式配置确保代码可维护性
- 模块化的 Store 架构 — 支持单例模式、组合模式、独立功能模式等多种 Store 组织方式,适应不同场景
- 持久化策略成熟 — 白名单机制、过期时间、加密存储等,兼顾体验与安全