Files
RNAPP-skill/RN_EMPTY_CLIENT_INIT_GUIDE.md
T
2026-05-14 16:39:44 +08:00

460 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RN 空业务客户端初始化规则
本文档从当前客户端抽取可复用的工程骨架,用于初始化一个新的 React Native 项目。目标是保留架构、分层、导航、主题、基础设施和代码规范,不携带任何业务功能、业务命名、业务接口、业务图片或第三方业务 SDK。
## 当前客户端框架抽象
当前项目的客户端框架可以概括为:
- `App.tsx` 是应用组合根:挂载全局 Provider、状态栏、导航容器、全局反馈组件,并执行应用级初始化。
- `src/navigation/` 统一管理 React Navigation:包含 `NavigationContainer`、根 Stack、全局 `navigationRef` 和路由类型。
- `src/modules/` 按功能域组织页面,每个模块内部可拥有 `screens/``components/``viewmodels/``services/``models/``assets/`
- `src/services/` 放跨模块基础服务,例如 API Client、Storage、环境配置、日志、监控。业务服务应在对应模块内优先落地,只有真正跨模块复用时才上移。
- `src/models/` 放跨模块共享类型。模块私有类型放模块内部。
- `src/viewmodels/` 是历史全局 ViewModel 区域;新项目建议优先使用模块内 `viewmodels/`,只把跨模块状态放全局。
- `src/components/` 是无业务通用 UI 组件;业务组件必须放模块内。
- `src/theme/` 统一管理颜色、字体、间距、阴影、圆角等设计 token。
- `src/utils/` 放无状态、无业务或弱业务的工具函数;业务工具放模块内部。
- `src/contexts/` 放全局上下文,例如应用设置、会话态、网络态。禁止把页面局部状态放进全局 Context。
新项目初始化时应复用以上骨架,但不要复制当前项目中的任何业务模块、业务服务、业务模型、业务资源和业务文案。
## 初始化原则
1. 空项目只提供工程能力,不提供业务能力。
2. 默认使用 TypeScript 严格模式。
3. 默认采用模块化 MVVM,但初始化时只保留空模块示例或 `app` 壳模块。
4. View 不直接调用 Service;依赖方向为 `View -> ViewModel -> Service -> Model`
5. 全局目录只放跨模块能力;模块私有内容必须留在模块内部。
6. 导航只注册基础壳页面,例如 `Root``NotFound``DevEntry`。不要注册登录、首页、订单、视频、支付等业务路由。
7. API Client 可以初始化为空基础设施,但不得写任何真实业务 endpoint。
8. 主题可以有中性色、品牌占位色和基础尺寸,但不得包含旧项目品牌色、业务状态色或业务命名。
9. assets 只允许保留占位资源规范,不放旧项目图片、图标、启动页、业务素材。
10. 任何第三方 SDK 必须按基础设施价值引入;Firebase、CodePush、登录、支付、分享、视频等都不是空项目默认依赖。
## 推荐目录结构
```text
.
├── App.tsx
├── index.js
├── app.json
├── babel.config.js
├── metro.config.js
├── tsconfig.json
├── package.json
├── __tests__/
│ └── App.test.tsx
├── docs/
│ ├── ARCHITECTURE.md
│ ├── CODE_RULES.md
│ └── MODULE_RULES.md
└── src/
├── app/
│ ├── AppProviders.tsx
│ └── AppBootstrap.ts
├── assets/
│ └── index.ts
├── components/
│ ├── Button.tsx
│ ├── TextField.tsx
│ ├── EmptyState.tsx
│ └── index.ts
├── contexts/
│ └── AppContext.tsx
├── hooks/
│ └── index.ts
├── models/
│ ├── Api.ts
│ └── index.ts
├── modules/
│ ├── app-shell/
│ │ ├── screens/
│ │ │ ├── RootScreen.tsx
│ │ │ └── NotFoundScreen.tsx
│ │ └── index.ts
│ └── index.ts
├── navigation/
│ ├── AppNavigator.tsx
│ ├── Navigation.tsx
│ ├── NavigationService.ts
│ ├── routes.ts
│ └── types.ts
├── services/
│ ├── ApiClient.ts
│ ├── StorageService.ts
│ ├── config.ts
│ └── index.ts
├── theme/
│ ├── colors.ts
│ ├── spacing.ts
│ ├── typography.ts
│ └── index.ts
└── utils/
└── index.ts
```
## 最小依赖建议
`package.json` 初始依赖只放通用客户端能力:
```json
{
"dependencies": {
"@react-native-async-storage/async-storage": "latest",
"@react-navigation/native": "latest",
"@react-navigation/native-stack": "latest",
"react": "matching-react-native-version",
"react-native": "selected-version",
"react-native-gesture-handler": "latest",
"react-native-safe-area-context": "latest",
"react-native-screens": "latest"
},
"devDependencies": {
"@react-native/babel-preset": "matching-react-native-version",
"@react-native/eslint-config": "matching-react-native-version",
"@react-native/metro-config": "matching-react-native-version",
"@react-native/typescript-config": "matching-react-native-version",
"@types/jest": "latest",
"@types/react": "latest",
"eslint": "latest",
"jest": "latest",
"prettier": "latest",
"typescript": "latest"
}
}
```
不要默认加入:
- 登录相关 SDK
- Firebase / Crashlytics / Analytics
- CodePush
- 支付 SDK
- WebView
- 视频播放、视频编辑、图片选择器
- 图表库
- 业务图标库
- 任何旧项目私有包
这些依赖必须等真实业务需求出现后再引入。
## 根入口规则
`App.tsx` 只负责组合,不写业务判断。
```tsx
import React from 'react';
import { StatusBar, useColorScheme } from 'react-native';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { AppProviders } from './src/app/AppProviders';
import { Navigation } from './src/navigation/Navigation';
function App() {
const isDarkMode = useColorScheme() === 'dark';
return (
<SafeAreaProvider>
<AppProviders>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<Navigation />
</AppProviders>
</SafeAreaProvider>
);
}
export default App;
```
`src/app/AppProviders.tsx` 只挂载基础 Provider
```tsx
import React, { type ReactNode } from 'react';
interface AppProvidersProps {
children: ReactNode;
}
export function AppProviders({ children }: AppProvidersProps) {
return <>{children}</>;
}
```
如果未来接入全局弹窗、Toast、Query Client、状态管理库,应统一从这里进入。
## 导航初始化规则
初始化时只保留根壳页面和兜底页面。
```tsx
// src/navigation/routes.ts
export const ROUTES = {
ROOT: 'Root',
NOT_FOUND: 'NotFound',
} as const;
```
```ts
// src/navigation/types.ts
import { ROUTES } from './routes';
export type RootStackParamList = {
[ROUTES.ROOT]: undefined;
[ROUTES.NOT_FOUND]: undefined;
};
```
```tsx
// src/navigation/Navigation.tsx
import 'react-native-gesture-handler';
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { AppNavigator } from './AppNavigator';
import { navigationRef } from './NavigationService';
export function Navigation() {
return (
<NavigationContainer ref={navigationRef}>
<AppNavigator />
</NavigationContainer>
);
}
```
```tsx
// src/navigation/AppNavigator.tsx
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { RootScreen, NotFoundScreen } from '../modules/app-shell';
import { ROUTES } from './routes';
import type { RootStackParamList } from './types';
const Stack = createNativeStackNavigator<RootStackParamList>();
export function AppNavigator() {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name={ROUTES.ROOT} component={RootScreen} />
<Stack.Screen name={ROUTES.NOT_FOUND} component={NotFoundScreen} />
</Stack.Navigator>
);
}
```
## 模块规则
模块是业务或功能的最小组织单位。初始化项目只放 `app-shell`,用于承载空壳页面。
每个模块最多包含:
```text
module-name/
├── screens/
├── components/
├── viewmodels/
├── services/
├── models/
├── hooks/
├── utils/
├── assets/
└── index.ts
```
模块边界规则:
- 模块对外只通过 `index.ts` 暴露必要内容。
- 跨模块不要互相读取内部目录。
- 模块内部可以调用全局基础服务。
- 全局基础层不能反向依赖具体模块。
- 新业务出现时,新建模块,不要把页面堆进 `src/screens/`
## MVVM 分层规则
```text
Screen / Component
-> ViewModel / Hook
-> Service
-> Model / DTO
```
职责边界:
- `screens/`:页面布局、用户交互、导航调用。
- `components/`:展示组件,只接收 props,不知道业务流程。
- `viewmodels/`:页面状态、交互流程、输入校验、数据转换。
- `services/`:网络、缓存、系统能力封装。
- `models/`:类型定义,不写逻辑。
禁止:
- Screen 直接请求接口。
- Service 直接操作导航。
- Model 引入 React。
- 通用组件读取业务 Context。
- ViewModel 写样式或返回 JSX。
## 基础服务规则
初始化可保留空 API Client
```ts
// src/services/config.ts
export interface ApiResponse<T> {
data: T;
message?: string;
}
export class ApiError extends Error {
constructor(message: string, public statusCode?: number, public data?: unknown) {
super(message);
this.name = 'ApiError';
}
}
```
```ts
// src/services/ApiClient.ts
import { ApiError, type ApiResponse } from './config';
class ApiClient {
async get<T>(url: string, init?: RequestInit): Promise<ApiResponse<T>> {
return this.request<T>(url, { ...init, method: 'GET' });
}
async post<T>(url: string, body?: unknown, init?: RequestInit): Promise<ApiResponse<T>> {
return this.request<T>(url, {
...init,
method: 'POST',
body: body == null ? undefined : JSON.stringify(body),
});
}
private async request<T>(url: string, init: RequestInit): Promise<ApiResponse<T>> {
const response = await fetch(url, {
...init,
headers: {
'Content-Type': 'application/json',
...init.headers,
},
});
const data = await response.json().catch(() => undefined);
if (!response.ok) {
throw new ApiError('Request failed', response.status, data);
}
return data as ApiResponse<T>;
}
}
export const apiClient = new ApiClient();
```
注意:空项目不要配置真实 baseURL、token 刷新、401 跳转、业务错误码、埋点字段。等业务协议确定后再加。
## 主题规则
主题只放基础设计 token
```ts
// src/theme/colors.ts
export const colors = {
background: '#FFFFFF',
surface: '#F7F8FA',
textPrimary: '#111827',
textSecondary: '#6B7280',
border: '#E5E7EB',
primary: '#2563EB',
danger: '#DC2626',
success: '#16A34A',
};
```
```ts
// src/theme/spacing.ts
export const spacing = {
xs: 4,
sm: 8,
md: 12,
lg: 16,
xl: 24,
xxl: 32,
};
```
不要出现旧项目品牌名、旧项目页面色、旧项目业务状态色。
## 代码规范
- 文件命名:组件和页面使用 `PascalCase.tsx`Hooks 使用 `useXxx.ts`,工具函数使用 `camelCase.ts`
- 组件导出:优先命名导出,避免默认导出散落。
- 类型:禁止 `any`,不确定时使用 `unknown`
- 样式:同文件 `StyleSheet.create`,共享 token 从 `src/theme` 读取。
- 导入顺序:React、React Native、第三方、项目内绝对路径、相对路径、类型。
- 注释:只解释复杂决策,不解释显而易见的代码。
- Barrel 文件:每个模块和全局目录可以有 `index.ts`,但不要导出内部实现细节。
## 初始化检查清单
新项目初始化完成后,必须确认:
- `npm run lint` 可运行。
- `npm run type-check` 可运行。
- `npm test` 至少有一个 App 渲染测试。
- App 启动后只进入空壳页面。
- `src/modules/` 只有 `app-shell` 或等价空壳模块。
- 没有登录、支付、视频、商品、用户、订单等业务命名。
- 没有真实 API 地址、密钥、token、业务错误码。
- 没有复制旧项目图片、图标、文案和私有包。
- `App.tsx` 没有业务初始化。
- 导航没有业务路由。
## 禁止从当前项目复制的内容
不要复制以下类别:
- `src/modules/auth`
- `src/modules/video`
- `src/modules/mainpage`
- `src/modules/marketing`
- `src/modules/profile`
- `src/modules/dataanalysis`
- `src/modules/TrainingQuiz`
- 任何具体业务 Service、Model、ViewModel
- Firebase、CodePush、TikTok、Apple/Google 登录、支付、推送、Universal Link 的现有实现
- `src/assets` 和各模块 `assets` 下的业务资源
- 旧项目 README 中的业务说明
可以借鉴但需要重写为无业务版本:
- MVVM 分层规范
- 模块化目录规则
- 导航服务模式
- 主题 token 组织方式
- API Client 基础封装模式
- lint、format、type-check、validate 脚本
## 给 AI 或团队的初始化指令
可以直接把下面这段作为新项目初始化提示词:
```text
请初始化一个 React Native + TypeScript 项目骨架,只保留工程架构,不包含任何业务。
要求:
1. 使用模块化 MVVM 架构。
2. 创建 App.tsx、src/app、src/navigation、src/modules/app-shell、src/components、src/services、src/models、src/theme、src/utils、src/hooks、src/contexts。
3. 导航只注册 Root 和 NotFound 两个无业务页面。
4. services 只提供空 ApiClient、StorageService、config 类型,不写真实接口、登录、token 或业务错误码。
5. components 只提供无业务基础组件,如 Button、TextField、EmptyState。
6. theme 只提供中性设计 token。
7. 不要创建登录、首页、商品、订单、视频、支付、用户中心等任何业务模块。
8. 不要复制任何旧项目图片、文案、品牌色、SDK 初始化或业务依赖。
9. package scripts 至少包含 start、ios、android、test、lint、format、type-check、validate。
10. TypeScript 开启 strict,并配置 src 路径别名。
```