@[toc]
如果你写 RN 写到后面,开始出现下面这些情况:
- 一个页面引用 5~8 个自定义 Hook
- 一个 Hook 内部 300 行,还不敢拆
- useEffect 嵌套 useEffect,依赖数组随缘写
- 改一个状态,引发一连串莫名其妙的更新
那问题往往不在 React Native,而在Hooks 的设计方式本身已经失控了。
这篇文章不讲“Hook 是什么”,而是讲:
在真实 RN 项目里,Hooks 应该怎么设计,哪些写法一定会把项目带沟里。
一、为什么 RN 项目里的 Hook 特别容易“写炸”?
先说一个现实结论:
Hook 是“状态和行为的放大器”,不是解药。
1. Hook 太容易承载“过多职责”
很多项目里的 Hook,最后都会长成这样:
functionusePageLogic(){// 请求// 权限判断// 数据转换// 埋点// 页面跳转}写的时候很爽,
三个月后——没人敢改。
2. useEffect 本身就是“隐性依赖地狱”
useEffect(()=>{doSomething(a,b,c)},[a,b])- 少写依赖 → 逻辑不一致
- 多写依赖 → 无限触发
- eslint 一关 → 世界和平(假的)
3. RN 的异步 + 生命周期放大问题
- 页面切后台
- 组件卸载
- 网络请求返回
Hook 如果没设计好,很容易:
- setState on unmounted component
- 重复请求
- 内存泄漏
二、一个健康 Hook 的设计目标
在进入规范前,先给你一个判断标准。
一个“设计良好”的 Hook,应该满足:
- 职责单一
- 可预测
- 可组合
- 不隐藏业务规则
如果一个 Hook 做不到这四点,迟早要拆。
三、Hook 设计的 5 条核心规范
规范 1:Hook 只做“状态编排”,不做业务裁决
错误示例:
functionuseUserPermission(user){if(user.role==='admin'){// ...}}正确做法:
// domain/user.tsexportfunctioncanEdit(user:User){returnuser.role==='admin'}// hooks/useUserPermission.tsexportfunctionuseUserPermission(user:User){returncanEdit(user)}判断规则属于 domain,不属于 hook。
规范 2:一个 Hook 只管理一类状态
反例(很常见):
functionusePage(){const[list,setList]=useState([])const[loading,setLoading]=useState(false)const[selected,setSelected]=useState(null)}正确拆法:
useListData()useLoadingState()useSelection()Hook 是组合单位,不是容器。
规范 3:不要在 Hook 里偷偷“改全局”
危险写法:
functionuseInit(){useEffect(()=>{store.setState(...)},[])}问题是:
- 页面一 mount 就改全局
- 调用方完全无感知
更好的方式是:
functionuseInit(){return()=>{store.setState(...)}}副作用必须显式触发。
规范 4:异步 Hook 必须考虑“取消和卸载”
错误示例:
useEffect(()=>{fetchData().then(setData)},[])改进版本:
useEffect(()=>{letmounted=truefetchData().then(res=>{if(mounted)setData(res)})return()=>{mounted=false}},[])这是 RN 项目里非常真实的坑。
规范 5:Hook 返回值要“语义清晰”
不要这样:
const[a,b,c]=useSomething()推荐这样:
const{data,loading,refresh}=useSomething()Hook 是接口,不是数组谜题。
四、useEffect 的正确使用姿势
一句话总结:
能不用 useEffect,就不用。
1. 能同步算出来的,不要进 effect
错误:
useEffect(()=>{setTotal(price*count)},[price,count])正确:
consttotal=price*count2. 派生状态 > effect 驱动状态
constfilteredList=useMemo(()=>{returnlist.filter(...)},[list])3. effect 只做三件事
- 网络请求
- 订阅 / 监听
- 与外部系统交互
其他情况,大概率是设计问题。
五、常见 Hook 反模式清单(重点)
反模式 1:巨型 Hook
usePageLogic()// 400 行症状:
- 无法复用
- 无法测试
- 无法拆
反模式 2:useEffect 充当“状态中转站”
useEffect(()=>{setB(calcA(a))},[a])这基本等同于:
- 手写响应式系统
- 极易失控
反模式 3:Hook 内部偷偷导航
navigation.navigate(...)页面不知道:
- 什么时候跳
- 为什么跳
反模式 4:Hook 强依赖页面结构
useScrollPosition(ref)ref 来自页面,生命周期复杂,极易出问题。
六、一个可运行的 Demo:拆解“失控 Hook”
原始写法
functionuseProfilePage(){const[user,setUser]=useState(null)const[loading,setLoading]=useState(false)useEffect(()=>{setLoading(true)fetchUser().then(res=>{setUser(res)setLoading(false)})},[])return{user,loading}}拆解后
functionuseUserData(){const[user,setUser]=useState(null)useEffect(()=>{fetchUser().then(setUser)},[])returnuser}functionuseLoading(initial=false){const[loading,setLoading]=useState(initial)return{loading,setLoading}}页面组合:
constuser=useUserData()const{loading}=useLoading()逻辑更清晰,也更容易复用。
七、真实项目里的变化
在一个中大型 RN 项目中,重构 Hooks 后:
- useEffect 数量 ↓ 40%+
- 页面逻辑可读性明显提升
- 新人调试 Hook 成本大幅降低
- “一改就炸”的情况明显减少
最后的总结
如果你记住这三点就够了:
- Hook 不是垃圾桶
- 业务规则永远不属于 Hook
- useEffect 是最后手段,不是默认选择
Hook 写得好,RN 项目会非常优雅;
Hook 写得乱,TS、Redux、架构都救不了你。