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、架构都救不了你。

相关推荐
长谷深风1117 小时前
Agent 何时该 Replan:五个关键判断
java·大数据·开发语言·ai agent·ai智能体·agent设计·clarify机制
lingran__7 小时前
C++ STL unordered系列(哈希) 底层剖析与模拟实现万字详解 | 基于哈希表,复刻 SGI-STL 泛型哈希容器架构
开发语言·c++·后端·哈希算法·哈希表·泛型编程·unordered系列
想要成为老金高手7 小时前
Nginx、PHP、Memcache、Tomcat 与 OpenResty 实验汇总
nginx·php·memcached
萧瑟余晖7 小时前
Java深入解析篇三十四之分布式事务
java·开发语言·分布式
CAE虚拟与现实7 小时前
源码注解 /class 注解 / 运行时注解三者生命周期
java·开发语言
有脚就行8 小时前
第24篇-Go-gRPC推理服务-高性能跨语言通信
开发语言·人工智能·后端·golang
码匠许师傅8 小时前
【C++ 面试真题】聊聊 C++ 的关联容器
开发语言·c++·面试
丰锋ff8 小时前
7.2Qt中Json的操作
开发语言·qt·json
我不是疯子是傻子8 小时前
用DeepSeek生成Qt多协议解析器?「正则+状态机+AI分支裁剪」混合调优实战
开发语言·qt
新鲜势力呀8 小时前
深入理解 Elliot CUDA 编程核心:PHP 实现 CUDA 并行计算思想与实践
android·开发语言·php