RN + TypeScript 项目越写越乱?如何规范架构?

@[toc]

如果你在 RN 项目里用了 TypeScript,但还是经常遇到下面这些情况,那你大概率不是一个人:

  • 业务一多,代码开始到处飞,找逻辑像在翻垃圾堆
  • hooks 套 hooks,useEffect 里再调 useEffect,自己都不敢改
  • 类型文件一堆,但和真实实现经常对不上
  • 新人一来就问:"这个状态到底在哪儿改的?"

这篇文章不是教你"TypeScript 怎么写",而是教你:
在 RN 项目里,TypeScript 怎么和架构一起用,才能不失控。

一、为什么 RN + TS 项目特别容易"写乱"?

先说一个结论:

项目乱,不是因为 TypeScript,而是"职责边界不清 + 状态乱流"。

1. 业务增长,逻辑开始横向扩散

最常见的场景:

  • 页面里写请求
  • hook 里也写请求
  • redux 里再写一套
  • utils 里还有一份"备用逻辑"

结果是:
一个需求改 4 个地方,还不一定改全。

2. Hooks 层级复杂,本来是解耦,最后变成"黑盒"

你可能写过类似这样的代码:

ts 复制代码
function usePageLogic() {
  const data = useFetch()
  const result = useProcess(data)
  useEffect(() => {
    doSomething(result)
  }, [result])
}

问题不是 hooks 多,而是:

  • hooks 里混了业务规则
  • hooks 之间有隐式依赖
  • 调试时根本不知道是哪一层出问题

3. 类型与实现分离,最后谁都不信类型

常见症状:

  • interface 写得很全
  • 实际接口返回偷偷多字段 / 少字段
  • any 越用越多
  • 最后 TS 只剩"自动补全工具"的作用

二、一个能长期维护的 RN + TS 项目结构

先给你一个推荐结构总览,这是我在中大型 RN 项目里反复验证过的。

txt 复制代码
src/
├── domain/        // 业务模型 & 业务规则
├── service/       // API / SDK / 数据来源
├── hooks/         // 可复用 hooks
├── store/         // 全局状态
├── ui/            // 纯 UI 组件
├── pages/         // 页面(只组装,不写重逻辑)
├── utils/
└── types/

接下来我们逐层拆。

三、domain 层:业务规则的"唯一入口"

domain 是整个项目的核心。

你应该在 domain 里放什么?

  • 业务实体(User、Order)
  • 业务规则(状态转换、校验)
  • 与 UI、网络无关的逻辑

示例:用户领域模型

ts 复制代码
// domain/user.ts
export interface User {
  id: string
  name: string
  role: 'admin' | 'user'
}

export function isAdmin(user: User) {
  return user.role === 'admin'
}

关键点:

  • domain 不 import RN、API、store
  • domain 只关心"业务正确性"

四、service 层:所有"数据来源"的统一出口

service 只做一件事:

把外部世界的数据,变成 domain 能用的数据。

示例:请求用户接口

ts 复制代码
// service/userService.ts
import { User } from '@/domain/user'

export async function fetchUser(): Promise<User> {
  const res = await fetch('/user')
  return res.json()
}

不要在页面里直接 fetch。

五、hooks 层:封装"状态 + 行为",但不写业务规则

一个非常重要的原则:

hooks = orchestration,不是 business logic。

示例:useUser

ts 复制代码
// hooks/useUser.ts
import { useEffect, useState } from 'react'
import { fetchUser } from '@/service/userService'
import { User } from '@/domain/user'

export function useUser() {
  const [user, setUser] = useState<User | null>(null)

  useEffect(() => {
    fetchUser().then(setUser)
  }, [])

  return user
}

如果你发现 hook 里开始写 if (role === 'admin')

那说明:逻辑该下沉到 domain 了。

六、UI 层:彻底"无脑"的组件

UI 组件只负责:

  • 接 props
  • 渲染
  • 触发回调

示例:用户卡片

tsx 复制代码
type Props = {
  name: string
  onPress: () => void
}

export function UserCard({ name, onPress }: Props) {
  return (
    <Pressable onPress={onPress}>
      <Text>{name}</Text>
    </Pressable>
  )
}

UI 层不 import hooks、不 import service。

七、页面(Page):只做"组装"

页面是"最脏但最轻"的一层。

tsx 复制代码
export function UserPage() {
  const user = useUser()

  if (!user) return null

  return (
    <UserCard
      name={user.name}
      onPress={() => {}}
    />
  )
}

页面:

  • 可以用 hook
  • 可以用 store
  • 但不写核心逻辑

八、全局状态方案怎么选?

这是 RN 项目里非常容易踩坑的一点。

Redux Toolkit

适合:

  • 状态多
  • 多页面共享
  • 对可预测性要求高

优点:

  • 规范
  • DevTools 强

缺点:

  • 样板代码略多

Zustand

适合:

  • 中小项目
  • 状态简单
  • 希望写得快
ts 复制代码
const useStore = create(set => ({
  count: 0,
  inc: () => set(state => ({ count: state.count + 1 }))
}))

Recoil

适合:

  • 状态依赖复杂
  • 类似图结构

但心里要有数:生态和长期维护要评估。

九、TypeScript 在 RN 项目的最佳实践

1. 类型从 domain 开始

不要一上来就写:

ts 复制代码
type Props = any

类型应该从业务模型流出。

2. 禁止"到处定义重复类型"

统一出口:

ts 复制代码
export type { User } from '@/domain/user'

3. 不要为了 TS 而 TS

有些地方允许不完美:

ts 复制代码
const ref = useRef<any>(null)

关键在于:核心业务链路一定要有类型。

十、常见反模式(请尽量避免)

1. 滥用 useEffect 做状态管理

ts 复制代码
useEffect(() => {
  setA(calcB(b))
}, [b])

这种写多了,状态会完全失控。

2. 一个 hook 管一切

ts 复制代码
usePageLogic()

内部 500 行代码,没人敢碰。

3. domain 被 UI 污染

ts 复制代码
import { Alert } from 'react-native'

一旦 domain import RN,架构就开始塌。

十一、真实项目里的收益

在真实 RN 项目中,用这套结构后:

  • 新需求平均开发时间 ↓ 30%+
  • Bug 定位明显更快
  • 新人 1~2 天能独立改需求
  • TS 不再是"摆设"

最后的总结

如果你只记住一句话:

RN + TypeScript 的核心不是"写类型",而是"让结构帮你兜住复杂度"。

TypeScript 是放大器:

  • 架构好 → TS 越用越爽
  • 架构乱 → TS 只会更痛苦
相关推荐
神算大模型APi--天枢6462 小时前
全栈自主可控:国产算力平台重塑大模型后端开发与部署生态
大数据·前端·人工智能·架构·硬件架构
苏打水com2 小时前
第十五篇:Day43-45 前端性能优化进阶——从“可用”到“极致”(对标职场“高并发场景优化”需求)
前端·css·vue·html·js
JS_GGbond2 小时前
用美食来理解JavaScript面向对象编程
开发语言·javascript·美食
@大迁世界2 小时前
08.CSS if() 函数
前端·css
Moment2 小时前
小米不仅造车,还造模型?309B参数全开源,深度思考完胜DeepSeek 🐒🐒🐒
前端·人工智能·后端
苏打水com2 小时前
第十六篇:Day46-48 前端安全进阶——从“漏洞防范”到“安全体系”(对标职场“攻防实战”需求)
前端·javascript·css·vue.js·html
5C242 小时前
从思想到实践:前端工程化体系与 Webpack 构建架构深度解析
前端·前端工程化
咕噜企业分发小米2 小时前
如何平衡服务器内存使用率和系统稳定性?
java·服务器·前端
前端无涯2 小时前
react组件(2)---State 与生命周期
前端·react.js