RN Hooks 设计规范与反模式清单

@[toc]

如果你写 RN 写到后面,开始出现下面这些情况:

  • 一个页面引用 5~8 个自定义 Hook
  • 一个 Hook 内部 300 行,还不敢拆
  • useEffect 嵌套 useEffect,依赖数组随缘写
  • 改一个状态,引发一连串莫名其妙的更新

那问题往往不在 React Native,而在 Hooks 的设计方式本身已经失控了

这篇文章不讲"Hook 是什么",而是讲:
在真实 RN 项目里,Hooks 应该怎么设计,哪些写法一定会把项目带沟里。

一、为什么 RN 项目里的 Hook 特别容易"写炸"?

先说一个现实结论:

Hook 是"状态和行为的放大器",不是解药。

1. Hook 太容易承载"过多职责"

很多项目里的 Hook,最后都会长成这样:

ts 复制代码
function usePageLogic() {
  // 请求
  // 权限判断
  // 数据转换
  // 埋点
  // 页面跳转
}

写的时候很爽,

三个月后------没人敢改。

2. useEffect 本身就是"隐性依赖地狱"

ts 复制代码
useEffect(() => {
  doSomething(a, b, c)
}, [a, b])
  • 少写依赖 → 逻辑不一致
  • 多写依赖 → 无限触发
  • eslint 一关 → 世界和平(假的)

3. RN 的异步 + 生命周期放大问题

  • 页面切后台
  • 组件卸载
  • 网络请求返回

Hook 如果没设计好,很容易:

  • setState on unmounted component
  • 重复请求
  • 内存泄漏

二、一个健康 Hook 的设计目标

在进入规范前,先给你一个判断标准。

一个"设计良好"的 Hook,应该满足:

  1. 职责单一
  2. 可预测
  3. 可组合
  4. 不隐藏业务规则

如果一个 Hook 做不到这四点,迟早要拆。

三、Hook 设计的 5 条核心规范

规范 1:Hook 只做"状态编排",不做业务裁决

错误示例:

ts 复制代码
function useUserPermission(user) {
  if (user.role === 'admin') {
    // ...
  }
}

正确做法:

ts 复制代码
// domain/user.ts
export function canEdit(user: User) {
  return user.role === 'admin'
}

// hooks/useUserPermission.ts
export function useUserPermission(user: User) {
  return canEdit(user)
}

判断规则属于 domain,不属于 hook。

规范 2:一个 Hook 只管理一类状态

反例(很常见):

ts 复制代码
function usePage() {
  const [list, setList] = useState([])
  const [loading, setLoading] = useState(false)
  const [selected, setSelected] = useState(null)
}

正确拆法:

ts 复制代码
useListData()
useLoadingState()
useSelection()

Hook 是组合单位,不是容器。

规范 3:不要在 Hook 里偷偷"改全局"

危险写法:

ts 复制代码
function useInit() {
  useEffect(() => {
    store.setState(...)
  }, [])
}

问题是:

  • 页面一 mount 就改全局
  • 调用方完全无感知

更好的方式是:

ts 复制代码
function useInit() {
  return () => {
    store.setState(...)
  }
}

副作用必须显式触发。

规范 4:异步 Hook 必须考虑"取消和卸载"

错误示例:

ts 复制代码
useEffect(() => {
  fetchData().then(setData)
}, [])

改进版本:

ts 复制代码
useEffect(() => {
  let mounted = true

  fetchData().then(res => {
    if (mounted) setData(res)
  })

  return () => {
    mounted = false
  }
}, [])

这是 RN 项目里非常真实的坑。

规范 5:Hook 返回值要"语义清晰"

不要这样:

ts 复制代码
const [a, b, c] = useSomething()

推荐这样:

ts 复制代码
const {
  data,
  loading,
  refresh
} = useSomething()

Hook 是接口,不是数组谜题。

四、useEffect 的正确使用姿势

一句话总结:

能不用 useEffect,就不用。

1. 能同步算出来的,不要进 effect

错误:

ts 复制代码
useEffect(() => {
  setTotal(price * count)
}, [price, count])

正确:

ts 复制代码
const total = price * count

2. 派生状态 > effect 驱动状态

ts 复制代码
const filteredList = useMemo(() => {
  return list.filter(...)
}, [list])

3. effect 只做三件事

  • 网络请求
  • 订阅 / 监听
  • 与外部系统交互

其他情况,大概率是设计问题。

五、常见 Hook 反模式清单(重点)

反模式 1:巨型 Hook

ts 复制代码
usePageLogic() // 400 行

症状:

  • 无法复用
  • 无法测试
  • 无法拆

反模式 2:useEffect 充当"状态中转站"

ts 复制代码
useEffect(() => {
  setB(calcA(a))
}, [a])

这基本等同于:

  • 手写响应式系统
  • 极易失控

反模式 3:Hook 内部偷偷导航

ts 复制代码
navigation.navigate(...)

页面不知道:

  • 什么时候跳
  • 为什么跳

反模式 4:Hook 强依赖页面结构

ts 复制代码
useScrollPosition(ref)

ref 来自页面,生命周期复杂,极易出问题。

六、一个可运行的 Demo:拆解"失控 Hook"

原始写法

ts 复制代码
function useProfilePage() {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    setLoading(true)
    fetchUser().then(res => {
      setUser(res)
      setLoading(false)
    })
  }, [])

  return { user, loading }
}

拆解后

ts 复制代码
function useUserData() {
  const [user, setUser] = useState(null)

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

  return user
}

function useLoading(initial = false) {
  const [loading, setLoading] = useState(initial)
  return { loading, setLoading }
}

页面组合:

ts 复制代码
const user = useUserData()
const { loading } = useLoading()

逻辑更清晰,也更容易复用。

七、真实项目里的变化

在一个中大型 RN 项目中,重构 Hooks 后:

  • useEffect 数量 ↓ 40%+
  • 页面逻辑可读性明显提升
  • 新人调试 Hook 成本大幅降低
  • "一改就炸"的情况明显减少

最后的总结

如果你记住这三点就够了:

  1. Hook 不是垃圾桶
  2. 业务规则永远不属于 Hook
  3. useEffect 是最后手段,不是默认选择

Hook 写得好,RN 项目会非常优雅;

Hook 写得乱,TS、Redux、架构都救不了你。

相关推荐
BingoGo21 小时前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack21 小时前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack4 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理4 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
feifeigo1234 天前
matlab画图工具
开发语言·matlab
dustcell.4 天前
haproxy七层代理
java·开发语言·前端