跳到主要内容

3. 文件结构

Expo官方模版的基本目录和根文件

名称类型用途说明
app文件夹包含应用的导航结构(采用基于文件的导航方式)。
app 目录的文件结构将决定应用的导航结构。
这个应用目前有两个路由,它们由两个文件定义:
app/(tabs)/index.tsxapp/(tabs)/explore.tsx
而位于 app/(tabs)/_layout.tsx 的布局文件用于设置标签页导航器(tab navigator)。
assets文件夹assets 目录包含用于应用图标的 adaptive-icon.png(Android 使用)和 icon.png(iOS 使用)。
它还包含项目启动屏所使用的图片 splash.png,以及当应用在浏览器中运行时使用的 favicon.png
components文件夹包含 React Native 组件,例如 ThemedText.tsx,它用于创建会根据应用的浅色模式和深色模式切换配色方案的文本元素。
constants文件夹包含 Colors.ts,它存放了应用中使用的所有颜色值列表。
hooks文件夹包含 React Hooks,用于在组件之间共享常用行为。例如,useThemeColor() 是一个 Hook,根据当前主题返回对应的颜色。
scripts文件夹包含 reset-project.js,可以通过 npm run reset-project 运行。
该脚本会将现有的 app 目录重命名为 app-example,并创建一个新的 app 目录,其中包含一个 index.tsx 文件。
app.json文件包含项目的配置选项,被称为应用配置(app config)。
这些选项会在开发、构建、提交和更新应用时改变项目的行为。
package.json文件package.json 文件包含项目的依赖、脚本和元数据。
每当向项目添加新的依赖时,该依赖都会被记录在此文件中。
tsconfig.json文件包含 TypeScript 用来在整个项目中强制类型安全的规则。

为了根目录文件夹数量控制,方便查看,我个人会把一些和功能强相关的目录和文件放到/src下。当然这样以来我们就需要再tsconfig.json中做一些配置了,因为我喜欢用Path Mapping(路径映射)。 这包括如app/components/constants/hooks等目录。

我的一个目录设置案例

执行:

tree -L 2 -d -I "node_modules|.git"

.
├── _other
├── android
│ ├── app
│ ├── build
│ └── gradle
├── assets
│ ├── fonts
│ ├── images
│ └── lottie
├── ios
│ ├── build
│ ├── Pods
│ ├── yi40
│ ├── yi40.xcodeproj
│ └── yi40.xcworkspace
├── lib
│ ├── hexagram
│ ├── interpretations
│ ├── jingfang
│ └── yilin
├── locale
│ ├── common
│ ├── meta
│ └── yi
├── scripts
├── src
│ ├── app
│ ├── app-types
│ ├── cloud
│ ├── components
│ ├── constants
│ ├── hooks
│ ├── style
│ └── utils
└── tests

可见我把一些内容放到src中了。

  • 根目录下: android/ios是expo prebuild后生成的原生目录。 _other是我放置特别文件的目录。 tests中我放了一些测试用组件。 lib专门用了放大量结构化文档包括数据文件。 locale是i18n多语言相关翻译文件。 scripts我基本没用到,留着吧。

  • src下 hooks技术性太强,复杂的项目我还是喜欢按功能模块把各个hooks提取出来单独放,而不是统一在一个hooks目录下。 一些通用工具类方法,我会加一个utils目录。 自定义的typescript类型,我会加一个app-types目录。 云服务相关,可能会加个cloud或server目录。

路径映射

TypeScript 的 “路径映射” 功能。

  • 英文学名Path Mapping(或 Paths Mapping
  • 中文学名路径映射

具体解释:

  • tsconfig.jsoncompilerOptions.paths 中配置别名,可以让你在项目中用短路径引用模块,而不必写相对路径(如 ../../../lib)。
  • baseUrl 一般需要设置为项目根目录(或某个起点),然后 paths 里配置别名对应实际路径。

我现在喜欢这样映射:


"compilerOptions": {

"strict": true,

"paths": {

"@/*": [

"./src/*"

],

// 资源文件别名

"@assets/*": [

"./assets/*"

],

// 公共库别名

"@lib/*": [

"./lib/*"

],

// 语言

"@locale/*": [

"./locale/*"

],

// test

"@tests/*": [

"./tests/*"

]

},

就相当于定义了几个常用别名,方便引用(而不用写长长的相对路径,而且便于移动目录和迁移):

import Button from "@/components/Button"; // src下的...
import logo from "@assets/logo.png"; // 根目录下assets
import { fetchData } from "@lib/api"; // 根目录下lib

独立模块化的目录设计

有时候我想把一些相对通用的功能包单独放在自己的目录中,位了方便迁移和复用,如AI、广告、内购等,其中涉及的types, hooks,组件或页面不分布在src/components等通用目录下,而是独立配置。

总体思路

  • 每个功能包(feature module)都作为一个独立目录存在,比如:ai/ads/iap/(内购)
  • 每个功能包内部自包含:有自己的组件(components)、hooks、types、页面(pages)等
  • 对外暴露统一的接口/导出文件(index.ts),方便其他模块直接 import
  • 不必把这些东西拆散到 src/componentssrc/hooks 等通用目录里,否则迁移/复用成本高

推荐目录结构

假设你在 src/features 下创建功能包:


src/
├─ features/
│ ├─ ai/
│ │ ├─ components/
│ │ │ └─ AIButton.tsx
│ │ ├─ hooks/
│ │ │ └─ useAIQuery.ts
│ │ ├─ types/
│ │ │ └─ ai.d.ts
│ │ ├─ pages/
│ │ │ └─ AIChatScreen.tsx
│ │ └─ index.ts <-- 对外统一出口
│ ├─ ads/
│ │ ├─ components/
│ │ ├─ hooks/
│ │ ├─ types/
│ │ └─ index.ts
│ └─ iap/
│ ├─ components/
│ ├─ hooks/
│ ├─ types/
│ └─ index.ts

index.ts 对外统一出口

每个模块的 index.ts 可以统一导出:

// src/features/ai/index.ts export * from './components/AIButton'; export * from './hooks/useAIQuery'; export * from './types/ai'; export * from './pages/AIChatScreen';

这样在其他地方就可以直接:

import { AIButton, useAIQuery, AIChatScreen } from '@/features/ai';

无需关心内部具体路径,非常方便迁移和复用。

路径映射(可选,但强烈推荐)

tsconfig.json 里加路径别名:

"paths": { "@features/*": ["./src/features/*"] }

这样导入更简洁:

import { AIButton } from '@features/ai';


设计原则

  1. 模块自包含:每个 feature 目录下可以有自己的 types/hooks/components/pages

  2. 对外统一接口:通过 index.ts 暴露,避免 import 内部实现路径

  3. 路径别名:保持 import 简洁,方便迁移

  4. 保持 App Router 不受影响:如果模块里有页面要作为路由,最好在 app/ 里建对应路由,再通过 features import 模块页面


优点

  • ✅ 易迁移:整个模块可以直接搬到另一个项目

  • ✅ 易复用:不同功能模块之间解耦

  • ✅ 清晰结构:src/features/xxx 一目了然

  • ✅ 路径别名 + index.ts 让 import 非常干净